"Hacktree" is now known as "OSTree"
authorColin Walters <walters@verbum.org>
Tue, 18 Oct 2011 18:44:48 +0000 (14:44 -0400)
committerColin Walters <walters@verbum.org>
Tue, 18 Oct 2011 18:44:48 +0000 (14:44 -0400)
It just sounds better.

56 files changed:
Makefile-hacktree.am [deleted file]
Makefile-src.am [new file with mode: 0644]
Makefile.am
README.md
configure.ac
hacktree.doap
parallel-debian/README-testing-multiroot.md
src/ht-builtin-checkout.c [deleted file]
src/ht-builtin-commit.c [deleted file]
src/ht-builtin-fsck.c [deleted file]
src/ht-builtin-init.c [deleted file]
src/ht-builtin-link-file.c [deleted file]
src/ht-builtin-log.c [deleted file]
src/ht-builtin-show.c [deleted file]
src/ht-builtins.h [deleted file]
src/libhacktree/hacktree-core.c [deleted file]
src/libhacktree/hacktree-core.h [deleted file]
src/libhacktree/hacktree-repo.c [deleted file]
src/libhacktree/hacktree-repo.h [deleted file]
src/libhacktree/hacktree-types.h [deleted file]
src/libhacktree/hacktree.h [deleted file]
src/libhtutil/ht-gio-utils.c [deleted file]
src/libhtutil/ht-gio-utils.h [deleted file]
src/libhtutil/ht-unix-utils.c [deleted file]
src/libhtutil/ht-unix-utils.h [deleted file]
src/libhtutil/htutil.h [deleted file]
src/libostree/ostree-core.c [new file with mode: 0644]
src/libostree/ostree-core.h [new file with mode: 0644]
src/libostree/ostree-repo.c [new file with mode: 0644]
src/libostree/ostree-repo.h [new file with mode: 0644]
src/libostree/ostree-types.h [new file with mode: 0644]
src/libostree/ostree.h [new file with mode: 0644]
src/libotutil/ot-gio-utils.c [new file with mode: 0644]
src/libotutil/ot-gio-utils.h [new file with mode: 0644]
src/libotutil/ot-unix-utils.c [new file with mode: 0644]
src/libotutil/ot-unix-utils.h [new file with mode: 0644]
src/libotutil/otutil.h [new file with mode: 0644]
src/main.c
src/ot-builtin-checkout.c [new file with mode: 0644]
src/ot-builtin-commit.c [new file with mode: 0644]
src/ot-builtin-fsck.c [new file with mode: 0644]
src/ot-builtin-init.c [new file with mode: 0644]
src/ot-builtin-link-file.c [new file with mode: 0644]
src/ot-builtin-log.c [new file with mode: 0644]
src/ot-builtin-show.c [new file with mode: 0644]
src/ot-builtins.h [new file with mode: 0644]
tests/libtest.sh
tests/t0000-init-link-files-fsck.sh
tests/t0001-init-out-of-tree.sh
tests/t0002-commit-one.sh
tests/t0003-commit-multiple.sh
tests/t0004-checkout-test1.sh
tests/t0005-nested-tree.sh
tests/t0006-removal.sh
tests/t0007-commit-stdin.sh
tests/t0008-log.sh

diff --git a/Makefile-hacktree.am b/Makefile-hacktree.am
deleted file mode 100644 (file)
index 636b7d0..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-# Makefile for C source code
-#
-# Copyright (C) 2011 Colin Walters <walters@verbum.org>
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-#
-# Author: Colin Walters <walters@verbum.org>
-
-noinst_LTLIBRARIES += libhtutil.la
-
-libhtutil_la_SOURCES = \
-       src/libhtutil/ht-unix-utils.c \
-       src/libhtutil/ht-unix-utils.h \
-       src/libhtutil/ht-gio-utils.c \
-       src/libhtutil/ht-gio-utils.h \
-       src/libhtutil/htutil.h \
-       $(NULL)
-libhtutil_la_CFLAGS = -I$(srcdir)/src/libhtutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS)
-libhtutil_la_LIBADD = $(GIO_UNIX_LIBS)
-
-noinst_LTLIBRARIES += libhacktree.la
-
-libhacktree_la_SOURCES = src/libhacktree/hacktree.h \
-       src/libhacktree/hacktree-core.c \
-       src/libhacktree/hacktree-core.h \
-       src/libhacktree/hacktree-repo.c \
-       src/libhacktree/hacktree-repo.h \
-       src/libhacktree/hacktree-types.h \
-       $(NULL)
-libhacktree_la_CFLAGS = -I$(srcdir)/src/libhacktree -I$(srcdir)/src/libhtutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS)
-libhacktree_la_LIBADD = libhtutil.la $(GIO_UNIX_LIBS)
-
-bin_PROGRAMS += hacktree
-
-hacktree_SOURCES = src/main.c \
-       src/ht-builtins.h \
-       src/ht-builtin-checkout.c \
-       src/ht-builtin-commit.c \
-       src/ht-builtin-fsck.c \
-       src/ht-builtin-init.c \
-       src/ht-builtin-link-file.c \
-       src/ht-builtin-log.c \
-       src/ht-builtin-show.c \
-       $(NULL)
-hacktree_CFLAGS = -I$(srcdir)/src -I$(srcdir)/src/libhacktree -I$(srcdir)/src/libhtutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS)
-hacktree_LDADD = libhtutil.la libhacktree.la $(GIO_UNIX_LIBS)
diff --git a/Makefile-src.am b/Makefile-src.am
new file mode 100644 (file)
index 0000000..68cd3b5
--- /dev/null
@@ -0,0 +1,58 @@
+# Makefile for C source code
+#
+# Copyright (C) 2011 Colin Walters <walters@verbum.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# Author: Colin Walters <walters@verbum.org>
+
+noinst_LTLIBRARIES += libotutil.la
+
+libotutil_la_SOURCES = \
+       src/libotutil/ot-unix-utils.c \
+       src/libotutil/ot-unix-utils.h \
+       src/libotutil/ot-gio-utils.c \
+       src/libotutil/ot-gio-utils.h \
+       src/libotutil/otutil.h \
+       $(NULL)
+libotutil_la_CFLAGS = -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS)
+libotutil_la_LIBADD = $(GIO_UNIX_LIBS)
+
+noinst_LTLIBRARIES += libostree.la
+
+libostree_la_SOURCES = src/libostree/ostree.h \
+       src/libostree/ostree-core.c \
+       src/libostree/ostree-core.h \
+       src/libostree/ostree-repo.c \
+       src/libostree/ostree-repo.h \
+       src/libostree/ostree-types.h \
+       $(NULL)
+libostree_la_CFLAGS = -I$(srcdir)/src/libostree -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS)
+libostree_la_LIBADD = libotutil.la $(GIO_UNIX_LIBS)
+
+bin_PROGRAMS += ostree
+
+ostree_SOURCES = src/main.c \
+       src/ot-builtins.h \
+       src/ot-builtin-checkout.c \
+       src/ot-builtin-commit.c \
+       src/ot-builtin-fsck.c \
+       src/ot-builtin-init.c \
+       src/ot-builtin-link-file.c \
+       src/ot-builtin-log.c \
+       src/ot-builtin-show.c \
+       $(NULL)
+ostree_CFLAGS = -I$(srcdir)/src -I$(srcdir)/src/libostree -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS)
+ostree_LDADD = libotutil.la libostree.la $(GIO_UNIX_LIBS)
index 9bc67c9d6f07bb5fb0738f25d967d6579ff18234..8ff33df35184524dfd6e8be05dbd31becd71b85e 100644 (file)
@@ -10,5 +10,4 @@ libexec_PROGRAMS =
 noinst_LTLIBRARIES =
 noinst_PROGRAMS =
 
-
-include Makefile-hacktree.am
+include Makefile-src.am
index ca6eb5b2da1ac024b5a9f1e54c0ab0147dd551e8..3ceb5f489c5520cb8809b610481c266dbcbefd47 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-Hacktree
+OSTree
 ========
 
 Problem statement
@@ -48,7 +48,7 @@ Comparison with existing tools
     the old package.  
 
     This is most realistic option for people hacking on system
-    components currently, but hacktree will be better.
+    components currently, but ostree will be better.
 
  - LXC / containers
 
@@ -66,7 +66,7 @@ Comparison with existing tools
     this means you can't build NetworkManager, and thus are permanently
     stuck on whatever the distro provides.
 
-Who is hacktree for?
+Who is ostree for?
 ------------------------------
 
 First - operating system developers and testers.  I specifically keep
@@ -103,22 +103,19 @@ Debian).  It has a root filesystem like this:
 Now, what we can do is have a system that installs chroots as a subdirectory
 of the root, like:
 
-               /usr
-               /etc
-               /home
-               /gnomeos/root-3.0-opt/{usr,etc,var,...}
-               /gnomeos/root-3.2-opt/{usr,etc,var,...}
+               /ostree/gnomeos-3.0-opt-393a4555/{usr,etc,sbin,...}
+               /ostree/gnomeos-3.2-opt-7e9788a2/{usr,etc,sbin,...}
 
 These live in the same root filesystem as your regular distribution
 (Note though, the root partition should be reasonably sized, or
 hopefully you've used just one big partition).
 
-You should be able to boot into one of these roots.  Since hacktree
+You should be able to boot into one of these roots.  Since ostree
 lives inside a distro created partition, a tricky part here is that we
 need to know how to interact with the installed distribution's grub.
 This is an annoying but tractable problem.
 
-Hacktree will allow efficiently parallel installing and downloading OS
+OSTree will allow efficiently parallel installing and downloading OS
 builds.
 
 An important note here is that we explicitly link /home in each root
@@ -126,6 +123,40 @@ to the real /home.  This means you have your data.  This also implies
 we share uid/gid, so /etc/passwd will have to be in sync.  Probably
 what we'll do is have a script to pull the data from the "host" OS.
 
+Other shared directories are /var and /root.  Note that /etc is
+explicitly NOT shared!
+
+On a pure OSTree system, the filesystem layout will look like this:
+
+               .
+               |-- boot
+               |-- home
+               |-- ostree
+               |   |-- current -> gnomeos-3.2-opt-7e9788a2
+               |   |-- gnomeos-3.0-opt-393a4555
+               |   |   |-- etc
+               |   |   |-- lib
+               |   |   |-- mnt
+               |   |   |-- proc
+               |   |   |-- run
+               |   |   |-- sbin
+               |   |   |-- srv
+               |   |   |-- sys
+               |   |   `-- usr
+               |   `-- gnomeos-3.2-opt-7e9788a2
+               |       |-- etc
+               |       |-- lib
+               |       |-- mnt
+               |       |-- proc
+               |       |-- run
+               |       |-- sbin
+               |       |-- srv
+               |       |-- sys
+               |       `-- usr
+               |-- root
+               `-- var
+               
+
 Making this efficient
 ---------------------
 
@@ -142,14 +173,14 @@ that point to it.  This mutability means that we have to either:
     This is probably tractable over a long period of time, but if anything
     has a bug, then it corrupts the file effectively.
  2. Make the core OS read-only, with a well-defined mechanism for mutating
-    under the control of hacktree.
+    under the control of ostree.
 
 I chose 2.
 
 A userspace content-addressed versioning filesystem
 ---------------------------------------------------
 
-At its very core, that's what hacktree is.  Just like git.  If you
+At its very core, that's what ostree is.  Just like git.  If you
 understand git, you know it's not like other revision control systems.
 git is effectively a specialized, userspace filesystem, and that is a
 very powerful idea.
@@ -162,16 +193,16 @@ source trees - it goes to effort to be sure it's compressing text for
 example, under the assumption that you have a lot of text.  Its
 handling of binaries is very generic and unoptimized.
 
-In contrast, hacktree is explicitly designed for binaries, and in
+In contrast, ostree is explicitly designed for binaries, and in
 particular one type of binary - ELF executables (or it will be once we
 start using bsdiff).  
 
-Another big difference versus git is that hacktree uses hard links
+Another big difference versus git is that ostree uses hard links
 between "checkouts" and the repository.  This means each checkout uses
 almost no additional space, and is *extremely* fast to check out.  We
 can do this because again each checkout is designed to be read-only.
 
-So we mentioned above the 
+So we mentioned above there are:
 
                /gnomeos/root-3.0-opt
                /gnomeos/root-3.2-opt
@@ -206,7 +237,7 @@ change =)
 Atomic upgrades, rollback
 -------------------------
 
-Hacktree is designed to atomically swap operating systems - such that
+OSTree is designed to atomically swap operating systems - such that
 during an upgrade and reboot process, you either get the full new
 system, or the old one.  There is no "Please don't turn off your
 computer".  We do this by simply using a symbolic link like:
@@ -272,13 +303,13 @@ What about "packages"?
 Basically I think they're a broken idea.  There are several different
 classes of things that demand targeted solutions:
 
- 1. Managing and upgrading the core OS (hacktree)
+ 1. Managing and upgrading the core OS (ostree)
  2. Managing and upgrading desktop applications (gnome-shell, glick?)
  3. System extensions - these are arbitrary RPMs like say the nVidia driver.
     We apply them after constructing each root.  Media codecs also fall
     into this category.
 
-How one might install say Apache on top of hacktree is an open
+How one might install say Apache on top of ostree is an open
 question - I think it probably makes sense honestly to ship services
 like this with no configuration - just the binaries.  Then admins can
 do whatever they want.
@@ -286,7 +317,7 @@ do whatever they want.
 Downloads
 ---------
 
-I'm pretty sure hacktree should be significantly better than RPM with
+I'm pretty sure ostree should be significantly better than RPM with
 deltarpms.  Note we only download changed objects.  If say just one
 translation changes, we only download that new translation!  One
 problem we will have to hunt down is programs that inject
@@ -350,7 +381,7 @@ approach is it doesn't solve the race conditions that happen when
 unpacking packages into the live system.  This problem is really
 important to me.
 
-Note though hacktree can definitely take advantage of BTRFS features!
+Note though ostree can definitely take advantage of BTRFS features!
 In particular, we could use "reflink"
 <http://lwn.net/Articles/331808/> instead of hard links, and avoid
 having the object store corrupted if somehow the files are modified
@@ -383,7 +414,7 @@ didn't use them:
  - Conary: <http://wiki.rpath.com/wiki/Conary:Updates_and_Rollbacks>
 
    If rpm/dpkg are like CVS, Conary is closer to Subversion.  It's not
-   bad, but hacktree is better than it for the exact same reasons git
+   bad, but ostree is better than it for the exact same reasons git
    is better than Subversion.
 
  - BTRFS: <http://en.wikipedia.org/wiki/Btrfs>
@@ -393,4 +424,4 @@ didn't use them:
  - Jhbuild: <https://live.gnome.org/Jhbuild>
 
    What we've been using in GNOME, and has the essential property of allowing you
-   to "fall back" to a stable system.  But hacktree will blow it out of the water.
+   to "fall back" to a stable system.  But ostree will blow it out of the water.
index 9bb579287160a282fdd237f6726b19c58f215dc6..43f18cc372a7866ddc33efc33ac668a9f9e9568e 100644 (file)
@@ -1,5 +1,5 @@
 AC_PREREQ([2.63])
-AC_INIT([hacktree], [0], [walters@verbum.org])
+AC_INIT([ostree], [0], [walters@verbum.org])
 AC_CONFIG_HEADER([config.h])
 AC_CONFIG_MACRO_DIR([m4])
 AC_CONFIG_AUX_DIR([build-aux])
index bb5fb5a50dffb366e3e5ec6b28093869426c2730..d6de92537aa7be52f8bc6cd1af8c149176eb3e7b 100644 (file)
@@ -5,8 +5,8 @@
          xmlns:gnome="http://api.gnome.org/doap-extensions#"
          xmlns="http://usefulinc.com/ns/doap#">
 
-  <name>hacktree</name>
-  <shortname>hacktree</shortname>
+  <name>ostree</name>
+  <shortname>ostree</shortname>
 
   <shortdesc xml:lang="en">GNOME OS build tool</shortdesc>
 
index 61279ea7f6d05a8c848f573638f1227690231093..4fd0a1238395c86ce5d85f79cccb4a6037f3153a 100644 (file)
@@ -37,8 +37,11 @@ parts of the OS to a new directory "r0".
        $ mkdir r0
        $ DIRS="bin dev etc lib lib32 lib64 media mnt opt proc root run sbin selinux srv sys tmp usr"
        $ mv $DIRS r0 
+       $ mkdir r0/{boot,var,home}
+       $ touch r0/{boot,var,home}/EMPTY
 
-Note that /boot, /home and /var are left shared.  Now with it still
+Note that /boot, /home and /var are left shared; we create empty
+destination directories that will be mounted over.  Now with it still
 mounted, we need to move on to the next part - modifying the initrd.
 
 Then I started hacking on the initrd, making understand how to chroot
diff --git a/src/ht-builtin-checkout.c b/src/ht-builtin-checkout.c
deleted file mode 100644 (file)
index 5ed32b9..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include "config.h"
-
-#include "ht-builtins.h"
-#include "hacktree.h"
-
-#include <glib/gi18n.h>
-
-static char *repo_path;
-
-static GOptionEntry options[] = {
-  { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", "repo" },
-  { NULL }
-};
-
-gboolean
-hacktree_builtin_checkout (int argc, char **argv, const char *prefix, GError **error)
-{
-  GOptionContext *context;
-  gboolean ret = FALSE;
-  HacktreeRepo *repo = NULL;
-  int i;
-  const char *commit;
-  const char *destination;
-
-  context = g_option_context_new ("COMMIT DESTINATION - Check out a commit into a filesystem tree");
-  g_option_context_add_main_entries (context, options, NULL);
-
-  if (!g_option_context_parse (context, &argc, &argv, error))
-    goto out;
-
-  if (repo_path == NULL)
-    repo_path = ".";
-
-  repo = hacktree_repo_new (repo_path);
-  if (!hacktree_repo_check (repo, error))
-    goto out;
-
-  if (argc < 3)
-    {
-      gchar *help = g_option_context_get_help (context, TRUE, NULL);
-      g_printerr ("%s\n", help);
-      g_free (help);
-      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                           "COMMIT and DESTINATION must be specified");
-      goto out;
-    }
-
-  commit = argv[1];
-  destination = argv[2];
-
-  if (!hacktree_repo_checkout (repo, commit, destination, error))
-    goto out;
-  ret = TRUE;
- out:
-  if (context)
-    g_option_context_free (context);
-  g_clear_object (&repo);
-  return ret;
-}
diff --git a/src/ht-builtin-commit.c b/src/ht-builtin-commit.c
deleted file mode 100644 (file)
index b26cbdb..0000000
+++ /dev/null
@@ -1,165 +0,0 @@
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include "config.h"
-
-#include "ht-builtins.h"
-#include "hacktree.h"
-
-#include <glib/gi18n.h>
-
-static char *repo_path;
-static gboolean separator_null;
-static int from_fd = -1;
-static gboolean from_stdin;
-static char *from_file;
-static char *subject;
-static char *body;
-static char **additions;
-static char **removals;
-
-static GOptionEntry options[] = {
-  { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", "repo" },
-  { "subject", 's', 0, G_OPTION_ARG_STRING, &subject, "One line subject", "subject" },
-  { "body", 'b', 0, G_OPTION_ARG_STRING, &body, "Full description", "body" },
-  { "from-fd", 0, 0, G_OPTION_ARG_INT, &from_fd, "Read new tree files from fd", "file descriptor" },
-  { "from-stdin", 0, 0, G_OPTION_ARG_NONE, &from_stdin, "Read new tree files from stdin", "file descriptor" },
-  { "from-file", 0, 0, G_OPTION_ARG_FILENAME, &from_file, "Read new tree files from another file", "path" },
-  { "separator-null", 0, 0, G_OPTION_ARG_NONE, &separator_null, "", "Use '\\0' as filename separator, as with find -print0" },
-  { "add", 'a', 0, G_OPTION_ARG_FILENAME_ARRAY, &additions, "Relative file path to add", "filename" },
-  { "remove", 'r', 0, G_OPTION_ARG_FILENAME_ARRAY, &removals, "Relative file path to remove", "filename" },
-  { NULL }
-};
-
-gboolean
-hacktree_builtin_commit (int argc, char **argv, const char *prefix, GError **error)
-{
-  GOptionContext *context;
-  gboolean ret = FALSE;
-  HacktreeRepo *repo = NULL;
-  gboolean using_filename_cmdline;
-  gboolean using_filedescriptors;
-  GPtrArray *additions_array = NULL;
-  GPtrArray *removals_array = NULL;
-  GChecksum *commit_checksum = NULL;
-  char **iter;
-
-  context = g_option_context_new ("- Commit a new revision");
-  g_option_context_add_main_entries (context, options, NULL);
-
-  if (!g_option_context_parse (context, &argc, &argv, error))
-    goto out;
-
-  if (repo_path == NULL)
-    repo_path = ".";
-  if (prefix == NULL)
-    prefix = ".";
-
-  repo = hacktree_repo_new (repo_path);
-  if (!hacktree_repo_check (repo, error))
-    goto out;
-
-  using_filename_cmdline = (removals || additions);
-  using_filedescriptors = (from_file || from_fd >= 0 || from_stdin);
-
-  if (!(using_filename_cmdline || using_filedescriptors))
-    {
-      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                           "No additions or removals specified");
-      goto out;
-    }
-  if (using_filename_cmdline && using_filedescriptors)
-    {
-      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                           "File descriptors may not be combined with --add or --remove");
-      goto out;
-    }
-
-  if (!subject)
-    {
-      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                           "A subject must be specified with --subject");
-      goto out;
-    }
-
-  if (using_filename_cmdline)
-    {
-      g_assert (removals || additions);
-      additions_array = g_ptr_array_new ();
-      removals_array = g_ptr_array_new ();
-
-      if (additions)
-        for (iter = additions; *iter; iter++)
-          g_ptr_array_add (additions_array, *iter);
-      if (removals)
-        for (iter = removals; *iter; iter++)
-          g_ptr_array_add (removals_array, *iter);
-      
-      if (!hacktree_repo_commit (repo, subject, body, NULL,
-                                 prefix, additions_array,
-                                 removals_array,
-                                 &commit_checksum,
-                                 error))
-        goto out;
-    }
-  else if (using_filedescriptors)
-    {
-      char separator = separator_null ? '\0' : '\n';
-      gboolean temp_fd = -1;
-
-      if (from_stdin)
-        from_fd = 0;
-      else if (from_file)
-        {
-          temp_fd = ht_util_open_file_read (from_file, error);
-          if (temp_fd < 0)
-            {
-              g_prefix_error (error, "Failed to open '%s': ", from_file);
-              goto out;
-            }
-          from_fd = temp_fd;
-        }
-      if (!hacktree_repo_commit_from_filelist_fd (repo, subject, body, NULL,
-                                                  prefix, from_fd, separator,
-                                                  &commit_checksum, error))
-        {
-          if (temp_fd >= 0)
-            close (temp_fd);
-          goto out;
-        }
-      if (temp_fd >= 0)
-        close (temp_fd);
-    }
-  ret = TRUE;
-  g_print ("%s\n", g_checksum_get_string (commit_checksum));
- out:
-  if (context)
-    g_option_context_free (context);
-  g_clear_object (&repo);
-  if (removals_array)
-    g_ptr_array_free (removals_array, TRUE);
-  if (additions_array)
-    g_ptr_array_free (additions_array, TRUE);
-  if (commit_checksum)
-    g_checksum_free (commit_checksum);
-  return ret;
-}
diff --git a/src/ht-builtin-fsck.c b/src/ht-builtin-fsck.c
deleted file mode 100644 (file)
index 3672524..0000000
+++ /dev/null
@@ -1,140 +0,0 @@
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include "config.h"
-
-#include "ht-builtins.h"
-#include "hacktree.h"
-
-#include <glib/gi18n.h>
-
-static char *repo_path;
-static gboolean quiet;
-
-static GOptionEntry options[] = {
-  { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", NULL },
-  { "quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, "Don't display informational messages", NULL },
-  { NULL }
-};
-
-typedef struct {
-  guint n_objects;
-} HtFsckData;
-
-static void
-object_iter_callback (HacktreeRepo  *repo,
-                      const char    *path,
-                      GFileInfo     *file_info,
-                      gpointer       user_data)
-{
-  HtFsckData *data = user_data;
-  struct stat stbuf;
-  GChecksum *checksum = NULL;
-  GError *error = NULL;
-  char *dirname = NULL;
-  char *checksum_prefix = NULL;
-  char *checksum_string = NULL;
-  char *filename_checksum = NULL;
-  char *dot;
-
-  dirname = g_path_get_dirname (path);
-  checksum_prefix = g_path_get_basename (dirname);
-  
-  /* nlinks = g_file_info_get_attribute_uint32 (file_info, "unix::nlink");
-     if (nlinks < 2 && !quiet)
-     g_printerr ("note: floating object: %s\n", path); */
-
-  if (!hacktree_stat_and_checksum_file (-1, path, &checksum, &stbuf, &error))
-    goto out;
-
-  filename_checksum = g_strdup (g_file_info_get_name (file_info));
-  dot = strrchr (filename_checksum, '.');
-  g_assert (dot != NULL);
-  *dot = '\0';
-
-  checksum_string = g_strconcat (checksum_prefix, filename_checksum, NULL);
-
-  if (strcmp (checksum_string, g_checksum_get_string (checksum)) != 0)
-    {
-      g_printerr ("ERROR: corrupted object '%s' expected checksum: %s\n",
-                  path, g_checksum_get_string (checksum));
-    }
-
-  data->n_objects++;
-
- out:
-  if (checksum != NULL)
-    g_checksum_free (checksum);
-  g_free (dirname);
-  g_free (checksum_prefix);
-  g_free (checksum_string);
-  g_free (filename_checksum);
-  if (error != NULL)
-    {
-      g_printerr ("%s\n", error->message);
-      g_clear_error (&error);
-    }
-}
-
-gboolean
-hacktree_builtin_fsck (int argc, char **argv, const char *prefix, GError **error)
-{
-  GOptionContext *context;
-  HtFsckData data;
-  gboolean ret = FALSE;
-  HacktreeRepo *repo = NULL;
-  const char *head;
-
-  context = g_option_context_new ("- Check the repository for consistency");
-  g_option_context_add_main_entries (context, options, NULL);
-
-  if (!g_option_context_parse (context, &argc, &argv, error))
-    goto out;
-
-  if (repo_path == NULL)
-    repo_path = ".";
-
-  data.n_objects = 0;
-
-  repo = hacktree_repo_new (repo_path);
-  if (!hacktree_repo_check (repo, error))
-    goto out;
-
-  if (!hacktree_repo_iter_objects (repo, object_iter_callback, &data, error))
-    goto out;
-
-  head = hacktree_repo_get_head (repo);
-  if (!head)
-    {
-      if (!quiet)
-        g_printerr ("No HEAD file\n");
-    }
-
-  if (!quiet)
-    g_printerr ("Total Objects: %u\n", data.n_objects);
-
-  ret = TRUE;
- out:
-  if (context)
-    g_option_context_free (context);
-  g_clear_object (&repo);
-  return ret;
-}
diff --git a/src/ht-builtin-init.c b/src/ht-builtin-init.c
deleted file mode 100644 (file)
index 4dc2d7f..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include "config.h"
-
-#include "ht-builtins.h"
-#include "hacktree.h"
-
-#include <glib/gi18n.h>
-
-static char *repo_path;
-static GOptionEntry options[] = {
-  { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", NULL },
-  { NULL }
-};
-
-gboolean
-hacktree_builtin_init (int argc, char **argv, const char *prefix, GError **error)
-{
-  GOptionContext *context = NULL;
-  gboolean ret = FALSE;
-  char *htdir_path = NULL;
-  char *objects_path = NULL;
-  GFile *htdir = NULL;
-  GFile *objects_dir = NULL;
-
-  context = g_option_context_new ("- Check the repository for consistency");
-  g_option_context_add_main_entries (context, options, NULL);
-
-  if (!g_option_context_parse (context, &argc, &argv, error))
-    goto out;
-
-  if (repo_path == NULL)
-    repo_path = ".";
-
-  htdir_path = g_build_filename (repo_path, HACKTREE_REPO_DIR, NULL);
-  htdir = ht_util_new_file_for_path (htdir_path);
-
-  if (!g_file_make_directory (htdir, NULL, error))
-    goto out;
-
-  objects_path = g_build_filename (htdir_path, "objects", NULL);
-  objects_dir = g_file_new_for_path (objects_path);
-  if (!g_file_make_directory (objects_dir, NULL, error))
-    goto out;
-  ret = TRUE;
- out:
-  if (context)
-    g_option_context_free (context);
-  g_free (htdir_path);
-  g_clear_object (&htdir);
-  return ret;
-}
diff --git a/src/ht-builtin-link-file.c b/src/ht-builtin-link-file.c
deleted file mode 100644 (file)
index 80f511d..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include "config.h"
-
-#include "ht-builtins.h"
-#include "hacktree.h"
-
-#include <glib/gi18n.h>
-
-static char *repo_path;
-static gboolean ignore_exists;
-static gboolean force;
-
-static GOptionEntry options[] = {
-  { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", "repo" },
-  { "ignore-exists", 'n', 0, G_OPTION_ARG_NONE, &ignore_exists, "Don't error if file exists", NULL },
-  { "force", 'f', 0, G_OPTION_ARG_NONE, &force, "If object exists, relink file", NULL },
-  { NULL }
-};
-
-gboolean
-hacktree_builtin_link_file (int argc, char **argv, const char *prefix, GError **error)
-{
-  GOptionContext *context;
-  gboolean ret = FALSE;
-  HacktreeRepo *repo = NULL;
-  int i;
-
-  context = g_option_context_new ("- Create a new hard link in the repository");
-  g_option_context_add_main_entries (context, options, NULL);
-
-  if (!g_option_context_parse (context, &argc, &argv, error))
-    goto out;
-
-  if (repo_path == NULL)
-    repo_path = ".";
-
-  repo = hacktree_repo_new (repo_path);
-  if (!hacktree_repo_check (repo, error))
-    goto out;
-
-  for (i = 0; i < argc-1; i++)
-    {
-      if (!hacktree_repo_link_file (repo, argv[i+1], ignore_exists, force, error))
-        goto out;
-    }
-  ret = TRUE;
- out:
-  if (context)
-    g_option_context_free (context);
-  g_clear_object (&repo);
-  return ret;
-}
diff --git a/src/ht-builtin-log.c b/src/ht-builtin-log.c
deleted file mode 100644 (file)
index aa0c271..0000000
+++ /dev/null
@@ -1,152 +0,0 @@
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include "config.h"
-
-#include "ht-builtins.h"
-#include "hacktree.h"
-
-#include <glib/gi18n.h>
-
-static char *repo_path;
-
-static GOptionEntry options[] = {
-  { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", "repo" },
-  { NULL }
-};
-
-gboolean
-hacktree_builtin_log (int argc, char **argv, const char *prefix, GError **error)
-{
-  GOptionContext *context;
-  gboolean ret = FALSE;
-  HacktreeRepo *repo = NULL;
-  GOutputStream *pager = NULL;
-  GVariant *commit = NULL;
-  char *head;
-
-  context = g_option_context_new ("- Show revision log");
-  g_option_context_add_main_entries (context, options, NULL);
-
-  if (!g_option_context_parse (context, &argc, &argv, error))
-    goto out;
-
-  if (repo_path == NULL)
-    repo_path = ".";
-  if (prefix == NULL)
-    prefix = ".";
-
-  repo = hacktree_repo_new (repo_path);
-  if (!hacktree_repo_check (repo, error))
-    goto out;
-
-  head = g_strdup (hacktree_repo_get_head (repo));
-  if (!head)
-    {
-      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "No HEAD exists");
-      goto out;
-    }
-
-  if (!ht_util_spawn_pager (&pager, error))
-    goto out;
-
-  while (TRUE)
-    {
-      HacktreeSerializedVariantType type;
-      char *formatted = NULL;
-      guint32 version;
-      const char *parent;
-      const char *subject;
-      const char *body;
-      guint64 timestamp;
-      const char *contents;
-      const char *root_metadata;
-      GDateTime *time_obj = NULL;
-      char *formatted_date = NULL;
-      const char *body_newline;
-      gsize bytes_written;
-      GVariant *commit_metadata = NULL;
-      char *formatted_metadata = NULL;
-      
-      if (commit)
-        g_variant_unref (commit);
-      if (!hacktree_repo_load_variant (repo, head, &type, &commit, error))
-        goto out;
-
-      /* Ignore commit metadata for now */
-      g_variant_get (commit, "(u@a{sv}&s&s&st&s&s)",
-                     &version, &commit_metadata, &parent, &subject, &body,
-                     &timestamp, &contents, &root_metadata);
-      time_obj = g_date_time_new_from_unix_utc (timestamp);
-      formatted_date = g_date_time_format (time_obj, "%a %b %d %H:%M:%S %Y %z");
-      g_date_time_unref (time_obj);
-      time_obj = NULL;
-
-      formatted_metadata = g_variant_print (commit_metadata, TRUE);
-      g_variant_unref (commit_metadata);
-      formatted = g_strdup_printf ("commit %s\nSubject: %s\nDate: %s\nMetadata: %s\n\n",
-                                   head, subject, formatted_date, formatted_metadata);
-      g_free (formatted_metadata);
-      g_free (formatted_date);
-      formatted_date = NULL;
-      
-      if (!g_output_stream_write_all (pager, formatted, strlen (formatted), &bytes_written, NULL, error))
-        {
-          g_free (formatted);
-          goto out;
-        }
-      g_free (formatted);
-      
-      body_newline = strchr (body, '\n');
-      do {
-        gsize len;
-        if (!g_output_stream_write_all (pager, "    ", 4, &bytes_written, NULL, error))
-          goto out;
-        len = body_newline ? body_newline - body : strlen (body);
-        if (!g_output_stream_write_all (pager, body, len, &bytes_written, NULL, error))
-          goto out;
-        if (!g_output_stream_write_all (pager, "\n\n", 2, &bytes_written, NULL, error))
-          goto out;
-        body_newline = strchr (body, '\n');
-        if (!body_newline)
-          break;
-        else
-          body_newline += 1;
-      } while (*body_newline);
-
-      if (strcmp (parent, "") == 0)
-        break;
-      g_free (head);
-      head = g_strdup (parent);
-    }
-
-  if (!g_output_stream_close (pager, NULL, error))
-    goto out;
-  ret = TRUE;
- out:
-  if (context)
-    g_option_context_free (context);
-  if (commit)
-    g_variant_unref (commit);
-  g_clear_object (&repo);
-  return ret;
-}
diff --git a/src/ht-builtin-show.c b/src/ht-builtin-show.c
deleted file mode 100644 (file)
index b9f30f2..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include "config.h"
-
-#include "ht-builtins.h"
-#include "hacktree.h"
-
-#include <glib/gi18n.h>
-
-static char *repo_path;
-
-static GOptionEntry options[] = {
-  { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", "repo" },
-  { NULL }
-};
-
-gboolean
-hacktree_builtin_show (int argc, char **argv, const char *prefix, GError **error)
-{
-  GOptionContext *context;
-  gboolean ret = FALSE;
-  HacktreeRepo *repo = NULL;
-  int i;
-  const char *target;
-  HacktreeSerializedVariantType type;
-  GVariant *variant = NULL;
-  char *formatted_variant = NULL;
-
-  context = g_option_context_new ("- Output a metadata object");
-  g_option_context_add_main_entries (context, options, NULL);
-
-  if (!g_option_context_parse (context, &argc, &argv, error))
-    goto out;
-
-  if (repo_path == NULL)
-    repo_path = ".";
-
-  repo = hacktree_repo_new (repo_path);
-  if (!hacktree_repo_check (repo, error))
-    goto out;
-
-  if (argc < 2)
-    {
-      target = hacktree_repo_get_head (repo);
-      if (!target)
-        {
-          g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                               "No arguments specified and no HEAD exists");
-          goto out;
-        }
-    }
-  else
-    target = argv[1];
-
-  if (!hacktree_repo_load_variant (repo, target, &type, &variant, error))
-    goto out;
-
-  g_print ("Object: %s\nType: %d\n", target, type);
-  formatted_variant = g_variant_print (variant, TRUE);
-  g_print ("%s\n", formatted_variant);
-  ret = TRUE;
- out:
-  if (context)
-    g_option_context_free (context);
-  g_clear_object (&repo);
-  if (variant)
-    g_variant_unref (variant);
-  g_free (formatted_variant);
-  return ret;
-}
diff --git a/src/ht-builtins.h b/src/ht-builtins.h
deleted file mode 100644 (file)
index 3cfe5b1..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#ifndef __HACKTREE_BUILTINS__
-#define __HACKTREE_BUILTINS__
-
-#include <glib-object.h>
-
-G_BEGIN_DECLS
-
-typedef enum {
-  HACKTREE_BUILTIN_FLAG_NONE = 0,
-} HacktreeBuiltinFlags;
-
-typedef struct {
-  const char *name;
-  gboolean (*fn) (int argc, char **argv, const char *prefix, GError **error);
-  int flags; /* HacktreeBuiltinFlags */
-} HacktreeBuiltin;
-
-gboolean hacktree_builtin_checkout (int argc, char **argv, const char *prefix, GError **error);
-gboolean hacktree_builtin_commit (int argc, char **argv, const char *prefix, GError **error);
-gboolean hacktree_builtin_init (int argc, char **argv, const char *prefix, GError **error);
-gboolean hacktree_builtin_log (int argc, char **argv, const char *prefix, GError **error);
-gboolean hacktree_builtin_link_file (int argc, char **argv, const char *prefix, GError **error);
-gboolean hacktree_builtin_fsck (int argc, char **argv, const char *prefix, GError **error);
-gboolean hacktree_builtin_show (int argc, char **argv, const char *prefix, GError **error);
-
-G_END_DECLS
-
-#endif
diff --git a/src/libhacktree/hacktree-core.c b/src/libhacktree/hacktree-core.c
deleted file mode 100644 (file)
index 9817c85..0000000
+++ /dev/null
@@ -1,272 +0,0 @@
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include "config.h"
-
-#include "hacktree.h"
-#include "htutil.h"
-
-#include <sys/types.h>
-#include <attr/xattr.h>
-
-static char *
-stat_to_string (struct stat *stbuf)
-{
-  return g_strdup_printf ("%u:%u:%u",
-                          (guint32)(stbuf->st_mode & ~S_IFMT),
-                          (guint32)stbuf->st_uid, 
-                          (guint32)stbuf->st_gid);
-}
-
-static char *
-canonicalize_xattrs (char *xattr_string, size_t len)
-{
-  char *p;
-  GSList *xattrs = NULL;
-  GSList *iter;
-  GString *result;
-
-  result = g_string_new (0);
-
-  p = xattr_string;
-  while (p < xattr_string+len)
-    {
-      xattrs = g_slist_prepend (xattrs, p);
-      p += strlen (p) + 1;
-    }
-
-  xattrs = g_slist_sort (xattrs, (GCompareFunc) strcmp);
-  for (iter = xattrs; iter; iter = iter->next)
-    g_string_append (result, iter->data);
-
-  g_slist_free (xattrs);
-  return g_string_free (result, FALSE);
-}
-
-static gboolean
-read_xattr_name_array (const char *path,
-                       const char *xattrs,
-                       size_t      len,
-                       GVariantBuilder *builder,
-                       GError  **error)
-{
-  gboolean ret = FALSE;
-  const char *p;
-
-  p = xattrs;
-  while (p < xattrs+len)
-    {
-      ssize_t bytes_read;
-      char *buf;
-
-      bytes_read = lgetxattr (path, p, NULL, 0);
-      if (bytes_read < 0)
-        {
-          ht_util_set_error_from_errno (error, errno);
-          goto out;
-        }
-      if (bytes_read == 0)
-        continue;
-
-      buf = g_malloc (bytes_read);
-      if (lgetxattr (path, p, buf, bytes_read) < 0)
-        {
-          ht_util_set_error_from_errno (error, errno);
-          g_free (buf);
-          goto out;
-        }
-      
-      g_variant_builder_add (builder, "(@ay@ay)",
-                             g_variant_new_bytestring (p),
-                             g_variant_new_fixed_array (G_VARIANT_TYPE ("y"), buf, bytes_read, 1));
-
-      g_free (buf);
-      p = p + strlen (p) + 1;
-    }
-  
-  ret = TRUE;
- out:
-  return ret;
-}
-
-GVariant *
-hacktree_get_xattrs_for_path (const char *path,
-                              GError    **error)
-{
-  GVariant *ret = NULL;
-  GVariantBuilder builder;
-  char *xattr_names = NULL;
-  char *xattr_names_canonical = NULL;
-  ssize_t bytes_read;
-
-  g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayay)"));
-
-  bytes_read = llistxattr (path, NULL, 0);
-
-  if (bytes_read < 0)
-    {
-      if (errno != ENOTSUP)
-        {
-          ht_util_set_error_from_errno (error, errno);
-          goto out;
-        }
-    }
-  else if (bytes_read > 0)
-    {
-      const char *p;
-      xattr_names = g_malloc (bytes_read);
-      if (llistxattr (path, xattr_names, bytes_read) < 0)
-        {
-          ht_util_set_error_from_errno (error, errno);
-          goto out;
-        }
-      xattr_names_canonical = canonicalize_xattrs (xattr_names, bytes_read);
-      
-      if (!read_xattr_name_array (path, xattr_names_canonical, bytes_read, &builder, error))
-        goto out;
-    }
-
-  ret = g_variant_builder_end (&builder);
- out:
-  if (!ret)
-    g_variant_builder_clear (&builder);
-  g_free (xattr_names);
-  g_free (xattr_names_canonical);
-  return ret;
-}
-
-gboolean
-hacktree_stat_and_checksum_file (int dir_fd, const char *path,
-                                 GChecksum **out_checksum,
-                                 struct stat *out_stbuf,
-                                 GError **error)
-{
-  GChecksum *content_sha256 = NULL;
-  GChecksum *content_and_meta_sha256 = NULL;
-  char *stat_string = NULL;
-  ssize_t bytes_read;
-  GVariant *xattrs = NULL;
-  int fd = -1;
-  DIR *temp_dir = NULL;
-  char *basename = NULL;
-  gboolean ret = FALSE;
-  char *symlink_target = NULL;
-  char *device_id = NULL;
-  struct stat stbuf;
-
-  basename = g_path_get_basename (path);
-
-  if (dir_fd == -1)
-    {
-      char *dirname = g_path_get_dirname (path);
-      temp_dir = opendir (dirname);
-      if (temp_dir == NULL)
-        {
-          ht_util_set_error_from_errno (error, errno);
-          g_free (dirname);
-        }
-      g_free (dirname);
-      dir_fd = dirfd (temp_dir);
-    }
-
-  if (fstatat (dir_fd, basename, &stbuf, AT_SYMLINK_NOFOLLOW) < 0)
-    {
-      ht_util_set_error_from_errno (error, errno);
-      goto out;
-    }
-
-  if (!S_ISLNK(stbuf.st_mode))
-    {
-      fd = ht_util_open_file_read_at (dir_fd, basename, error);
-      if (fd < 0)
-        {
-          ht_util_set_error_from_errno (error, errno);
-          goto out;
-        }
-    }
-
-  stat_string = stat_to_string (&stbuf);
-  xattrs = hacktree_get_xattrs_for_path (path, error);
-  if (!xattrs)
-    goto out;
-
-  content_sha256 = g_checksum_new (G_CHECKSUM_SHA256);
-  if (S_ISREG(stbuf.st_mode))
-    {
-      guint8 buf[8192];
-
-      while ((bytes_read = read (fd, buf, sizeof (buf))) > 0)
-        g_checksum_update (content_sha256, buf, bytes_read);
-      if (bytes_read < 0)
-        {
-          ht_util_set_error_from_errno (error, errno);
-          goto out;
-        }
-    }
-  else if (S_ISLNK(stbuf.st_mode))
-    {
-      symlink_target = g_malloc (PATH_MAX);
-
-      if (readlinkat (dir_fd, basename, symlink_target, PATH_MAX) < 0)
-        {
-          ht_util_set_error_from_errno (error, errno);
-          goto out;
-        }
-      g_checksum_update (content_sha256, (guint8*)symlink_target, strlen (symlink_target));
-    }
-  else if (S_ISCHR(stbuf.st_mode) || S_ISBLK(stbuf.st_mode))
-    {
-      device_id = g_strdup_printf ("%u", (guint)stbuf.st_rdev);
-      g_checksum_update (content_sha256, (guint8*)device_id, strlen (device_id));
-    }
-  else
-    {
-      g_set_error (error, G_IO_ERROR,
-                   G_IO_ERROR_FAILED,
-                   "Unsupported file '%s' (must be regular, symbolic link, or device)",
-                   path);
-      goto out;
-    }
-
-  content_and_meta_sha256 = g_checksum_copy (content_sha256);
-
-  g_checksum_update (content_and_meta_sha256, (guint8*)stat_string, strlen (stat_string));
-  g_checksum_update (content_and_meta_sha256, (guint8*)g_variant_get_data (xattrs), g_variant_get_size (xattrs));
-
-  *out_stbuf = stbuf;
-  *out_checksum = content_and_meta_sha256;
-  ret = TRUE;
- out:
-  if (fd >= 0)
-    close (fd);
-  if (temp_dir != NULL)
-    closedir (temp_dir);
-  g_free (symlink_target);
-  g_free (basename);
-  g_free (stat_string);
-  if (xattrs)
-    g_variant_unref (xattrs);
-  if (content_sha256)
-    g_checksum_free (content_sha256);
-
-  return ret;
-}
diff --git a/src/libhacktree/hacktree-core.h b/src/libhacktree/hacktree-core.h
deleted file mode 100644 (file)
index 5347299..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#ifndef _HACKTREE_CORE
-#define _HACKTREE_CORE
-
-#include <htutil.h>
-
-G_BEGIN_DECLS
-
-#define HACKTREE_EMPTY_STRING_SHA256 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
-
-typedef enum {
-  HACKTREE_OBJECT_TYPE_FILE = 1,
-  HACKTREE_OBJECT_TYPE_META = 2,
-} HacktreeObjectType;
-
-typedef enum {
-  HACKTREE_SERIALIZED_TREE_VARIANT = 1,
-  HACKTREE_SERIALIZED_COMMIT_VARIANT = 2,
-  HACKTREE_SERIALIZED_DIRMETA_VARIANT = 3,
-  HACKTREE_SERIALIZED_XATTR_VARIANT = 4
-} HacktreeSerializedVariantType;
-
-#define HACKTREE_SERIALIZED_VARIANT_FORMAT "(uv)"
-
-/*
- * xattr objects:
- * a(ayay) - array of (name, value) pairs, both binary data, though name is a bytestring
- */
-#define HACKTREE_XATTR_GVARIANT_FORMAT "a(ayay)"
-
-#define HACKTREE_DIR_META_VERSION 0
-/*
- * dirmeta objects:
- * u - Version
- * u - uid
- * u - gid
- * u - mode
- * a(ayay) - xattrs
- */
-#define HACKTREE_DIRMETA_GVARIANT_FORMAT "(uuuua(ayay))"
-
-#define HACKTREE_TREE_VERSION 0
-/*
- * Tree objects:
- * u - Version
- * a{sv} - Metadata
- * a(ss) - array of (filename, checksum) for files
- * a(sss) - array of (dirname, tree_checksum, meta_checksum) for directories
- */
-#define HACKTREE_TREE_GVARIANT_FORMAT "(ua{sv}a(ss)a(sss)"
-
-#define HACKTREE_COMMIT_VERSION 0
-/*
- * Commit objects:
- * u - Version
- * a{sv} - Metadata
- * s - parent checksum (empty string for initial)
- * s - subject 
- * s - body
- * t - Timestamp in seconds since the epoch (UTC)
- * s - Root tree contents
- * s - Root tree metadata
- */
-#define HACKTREE_COMMIT_GVARIANT_FORMAT "(ua{sv}ssstss)"
-
-GVariant *hacktree_get_xattrs_for_path (const char *path,
-                                        GError    **error);
-
-gboolean hacktree_stat_and_checksum_file (int dirfd, const char *path,
-                                          GChecksum **out_checksum,
-                                          struct stat *out_stbuf,
-                                          GError **error);
-
-
-#endif /* _HACKTREE_REPO */
diff --git a/src/libhacktree/hacktree-repo.c b/src/libhacktree/hacktree-repo.c
deleted file mode 100644 (file)
index 90b34ad..0000000
+++ /dev/null
@@ -1,1769 +0,0 @@
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include "config.h"
-
-#include "hacktree.h"
-#include "htutil.h"
-
-#include <gio/gunixoutputstream.h>
-#include <gio/gunixinputstream.h>
-
-static gboolean
-link_one_file (HacktreeRepo *self, const char *path,
-               HacktreeObjectType type,
-               gboolean ignore_exists, gboolean force,
-               GChecksum **out_checksum,
-               GError **error);
-static char *
-get_object_path (HacktreeRepo  *self,
-                 const char    *checksum,
-                 HacktreeObjectType type);
-
-enum {
-  PROP_0,
-
-  PROP_PATH
-};
-
-G_DEFINE_TYPE (HacktreeRepo, hacktree_repo, G_TYPE_OBJECT)
-
-#define GET_PRIVATE(o) \
-  (G_TYPE_INSTANCE_GET_PRIVATE ((o), HACKTREE_TYPE_REPO, HacktreeRepoPrivate))
-
-typedef struct _HacktreeRepoPrivate HacktreeRepoPrivate;
-
-struct _HacktreeRepoPrivate {
-  char *path;
-  GFile *repo_file;
-  char *head_ref_path;
-  char *objects_path;
-
-  gboolean inited;
-  char *current_head;
-};
-
-static void
-hacktree_repo_finalize (GObject *object)
-{
-  HacktreeRepo *self = HACKTREE_REPO (object);
-  HacktreeRepoPrivate *priv = GET_PRIVATE (self);
-
-  g_free (priv->path);
-  g_clear_object (&priv->repo_file);
-  g_free (priv->head_ref_path);
-  g_free (priv->objects_path);
-  g_free (priv->current_head);
-
-  G_OBJECT_CLASS (hacktree_repo_parent_class)->finalize (object);
-}
-
-static void
-hacktree_repo_set_property(GObject         *object,
-                          guint            prop_id,
-                          const GValue    *value,
-                          GParamSpec      *pspec)
-{
-  HacktreeRepo *self = HACKTREE_REPO (object);
-  HacktreeRepoPrivate *priv = GET_PRIVATE (self);
-
-  switch (prop_id)
-    {
-    case PROP_PATH:
-      priv->path = g_value_dup_string (value);
-      break;
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-      break;
-    }
-}
-
-static void
-hacktree_repo_get_property(GObject         *object,
-                          guint            prop_id,
-                          GValue          *value,
-                          GParamSpec      *pspec)
-{
-  HacktreeRepo *self = HACKTREE_REPO (object);
-  HacktreeRepoPrivate *priv = GET_PRIVATE (self);
-
-  switch (prop_id)
-    {
-    case PROP_PATH:
-      g_value_set_string (value, priv->path);
-      break;
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-      break;
-    }
-}
-
-static GObject *
-hacktree_repo_constructor (GType                  gtype,
-                           guint                  n_properties,
-                           GObjectConstructParam *properties)
-{
-  GObject *object;
-  GObjectClass *parent_class;
-  HacktreeRepoPrivate *priv;
-
-  parent_class = G_OBJECT_CLASS (hacktree_repo_parent_class);
-  object = parent_class->constructor (gtype, n_properties, properties);
-
-  priv = GET_PRIVATE (object);
-
-  g_assert (priv->path != NULL);
-  
-  priv->repo_file = ht_util_new_file_for_path (priv->path);
-  priv->head_ref_path = g_build_filename (priv->path, HACKTREE_REPO_DIR, "HEAD", NULL);
-  priv->objects_path = g_build_filename (priv->path, HACKTREE_REPO_DIR, "objects", NULL);
-
-  return object;
-}
-
-static void
-hacktree_repo_class_init (HacktreeRepoClass *klass)
-{
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-  g_type_class_add_private (klass, sizeof (HacktreeRepoPrivate));
-
-  object_class->constructor = hacktree_repo_constructor;
-  object_class->get_property = hacktree_repo_get_property;
-  object_class->set_property = hacktree_repo_set_property;
-  object_class->finalize = hacktree_repo_finalize;
-
-  g_object_class_install_property (object_class,
-                                   PROP_PATH,
-                                   g_param_spec_string ("path",
-                                                        "",
-                                                        "",
-                                                        NULL,
-                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-}
-
-static void
-hacktree_repo_init (HacktreeRepo *self)
-{
-}
-
-HacktreeRepo*
-hacktree_repo_new (const char *path)
-{
-  return g_object_new (HACKTREE_TYPE_REPO, "path", path, NULL);
-}
-
-static gboolean
-parse_checksum_file (HacktreeRepo   *self,
-                     const char     *path,
-                     char          **sha256,
-                     GError        **error)
-{
-  GError *temp_error = NULL;
-  gboolean ret = FALSE;
-  char *ret_sha256 = NULL;
-
-  ret_sha256 = ht_util_get_file_contents_utf8 (path, &temp_error);
-  if (ret_sha256 == NULL)
-    {
-      if (g_error_matches (temp_error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
-        {
-          g_clear_error (&temp_error);
-        }
-      else
-        {
-          g_propagate_error (error, temp_error);
-          goto out;
-        }
-    }
-  else
-    {
-      g_strchomp (ret_sha256);
-    }
-
-  *sha256 = ret_sha256;
-  ret = TRUE;
- out:
-  return ret;
-}
-
-static gboolean
-write_checksum_file (const char *path,
-                     const char *sha256,
-                     GError    **error)
-{
-  gboolean ret = FALSE;
-  char *buf = NULL;
-
-  buf = g_strconcat (sha256, "\n", NULL);
-  
-  if (!g_file_set_contents (path, buf, -1, error))
-    goto out;
-
-  ret = TRUE;
- out:
-  g_free (buf);
-  return ret;
-}
-
-gboolean
-hacktree_repo_check (HacktreeRepo *self, GError **error)
-{
-  HacktreeRepoPrivate *priv = GET_PRIVATE (self);
-
-  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
-
-  if (priv->inited)
-    return TRUE;
-
-  if (!g_file_test (priv->objects_path, G_FILE_TEST_IS_DIR))
-    {
-      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                   "Couldn't find objects directory '%s'", priv->objects_path);
-      return FALSE;
-    }
-  
-  priv->inited = TRUE;
-
-  return parse_checksum_file (self, priv->head_ref_path, &priv->current_head, error);
-}
-
-static gboolean
-import_gvariant_object (HacktreeRepo  *self,
-                        HacktreeSerializedVariantType type,
-                        GVariant       *variant,
-                        GChecksum    **out_checksum,
-                        GError       **error)
-{
-  HacktreeRepoPrivate *priv = GET_PRIVATE (self);
-  GVariant *serialized = NULL;
-  gboolean ret = FALSE;
-  gsize bytes_written;
-  char *tmp_name = NULL;
-  int fd = -1;
-  GUnixOutputStream *stream = NULL;
-
-  serialized = g_variant_new ("(uv)", (guint32)type, variant);
-
-  tmp_name = g_build_filename (priv->objects_path, "variant-tmp-XXXXXX", NULL);
-  fd = mkstemp (tmp_name);
-  if (fd < 0)
-    {
-      ht_util_set_error_from_errno (error, errno);
-      goto out;
-    }
-
-  stream = (GUnixOutputStream*)g_unix_output_stream_new (fd, FALSE);
-  if (!g_output_stream_write_all ((GOutputStream*)stream,
-                                  g_variant_get_data (serialized),
-                                  g_variant_get_size (serialized),
-                                  &bytes_written,
-                                  NULL,
-                                  error))
-    goto out;
-  if (!g_output_stream_close ((GOutputStream*)stream,
-                              NULL, error))
-    goto out;
-
-  if (!link_one_file (self, tmp_name, HACKTREE_OBJECT_TYPE_META, 
-                      TRUE, FALSE, out_checksum, error))
-    goto out;
-  
-  ret = TRUE;
- out:
-  /* Unconditionally unlink; if we suceeded, there's a new link, if not, clean up. */
-  (void) unlink (tmp_name);
-  if (fd != -1)
-    close (fd);
-  if (serialized != NULL)
-    g_variant_unref (serialized);
-  g_free (tmp_name);
-  g_clear_object (&stream);
-  return ret;
-}
-
-static gboolean
-load_gvariant_object_unknown (HacktreeRepo  *self,
-                              const char    *sha256,
-                              HacktreeSerializedVariantType *out_type,
-                              GVariant     **out_variant,
-                              GError       **error)
-{
-  GMappedFile *mfile = NULL;
-  gboolean ret = FALSE;
-  GVariant *ret_variant = NULL;
-  GVariant *container = NULL;
-  char *path = NULL;
-  guint32 ret_type;
-
-  path = get_object_path (self, sha256, HACKTREE_OBJECT_TYPE_META);
-  
-  mfile = g_mapped_file_new (path, FALSE, error);
-  if (mfile == NULL)
-    goto out;
-  else
-    {
-      container = g_variant_new_from_data (G_VARIANT_TYPE (HACKTREE_SERIALIZED_VARIANT_FORMAT),
-                                           g_mapped_file_get_contents (mfile),
-                                           g_mapped_file_get_length (mfile),
-                                           FALSE,
-                                           (GDestroyNotify) g_mapped_file_unref,
-                                           mfile);
-      if (!g_variant_is_of_type (container, G_VARIANT_TYPE (HACKTREE_SERIALIZED_VARIANT_FORMAT)))
-        {
-          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                       "Corrupted metadata object '%s'", sha256);
-          goto out;
-        }
-      g_variant_get (container, "(uv)",
-                     &ret_type, &ret_variant);
-      mfile = NULL;
-    }
-
-  ret = TRUE;
- out:
-  if (!ret)
-    {
-      if (ret_variant)
-        g_variant_unref (ret_variant);
-    }
-  else
-    {
-      *out_type = ret_type;
-      *out_variant = ret_variant;
-    }
-  if (container != NULL)
-    g_variant_unref (container);
-  g_free (path);
-  if (mfile != NULL)
-    g_mapped_file_unref (mfile);
-  return ret;
-}
-
-static gboolean
-load_gvariant_object (HacktreeRepo  *self,
-                      HacktreeSerializedVariantType expected_type,
-                      const char    *sha256, 
-                      GVariant     **out_variant,
-                      GError       **error)
-{
-  gboolean ret = FALSE;
-  HacktreeSerializedVariantType type;
-  GVariant *ret_variant = NULL;
-
-  if (!load_gvariant_object_unknown (self, sha256, &type, &ret_variant, error))
-    goto out;
-
-  if (type != expected_type)
-    {
-      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                   "Corrupted metadata object '%s'; found type %u, expected %u", sha256,
-                   type, (guint32)expected_type);
-      goto out;
-      
-    }
-
-  ret = TRUE;
-  *out_variant = ret_variant;
- out:
-  if (!ret)
-    {
-      if (ret_variant)
-        g_variant_unref (ret_variant);
-    }
-  return ret;
-}
-
-static gboolean
-import_directory_meta (HacktreeRepo  *self,
-                       const char *path,
-                       GVariant  **out_variant,
-                       GChecksum **out_checksum,
-                       GError    **error)
-{
-  gboolean ret = FALSE;
-  struct stat stbuf;
-  GChecksum *ret_checksum = NULL;
-  GVariant *dirmeta = NULL;
-  GVariant *xattrs = NULL;
-  gsize xattr_len;
-
-  if (lstat (path, &stbuf) < 0)
-    {
-      ht_util_set_error_from_errno (error, errno);
-      goto out;
-    }
-  
-  if (!S_ISDIR(stbuf.st_mode))
-    {
-      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                   "Not a directory: '%s'", path);
-      goto out;
-    }
-
-  xattrs = hacktree_get_xattrs_for_path (path, error);
-  if (!xattrs)
-    goto out;
-
-  dirmeta = g_variant_new ("(uuuu@a(ayay))",
-                           HACKTREE_DIR_META_VERSION,
-                           (guint32)stbuf.st_uid,
-                           (guint32)stbuf.st_gid,
-                           (guint32)(stbuf.st_mode & (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)),
-                           xattrs);
-  xattrs = NULL; /* was floating */
-  g_variant_ref_sink (dirmeta);
-
-  if (!import_gvariant_object (self, HACKTREE_SERIALIZED_DIRMETA_VARIANT, 
-                               dirmeta, &ret_checksum, error))
-        goto out;
-
-  ret = TRUE;
- out:
-  if (!ret)
-    {
-      if (ret_checksum)
-        g_checksum_free (ret_checksum);
-      if (dirmeta != NULL)
-        g_variant_unref (dirmeta);
-    }
-  else
-    {
-      *out_checksum = ret_checksum;
-      *out_variant = dirmeta;
-    }
-  if (xattrs)
-    g_variant_unref (xattrs);
-  return ret;
-}
-
-static char *
-get_object_path (HacktreeRepo  *self,
-                 const char    *checksum,
-                 HacktreeObjectType type)
-{
-  HacktreeRepoPrivate *priv = GET_PRIVATE (self);
-  char *checksum_prefix;
-  char *base_path;
-  char *ret;
-  const char *type_string;
-
-  checksum_prefix = g_strndup (checksum, 2);
-  base_path = g_build_filename (priv->objects_path, checksum_prefix, checksum + 2, NULL);
-  switch (type)
-    {
-    case HACKTREE_OBJECT_TYPE_FILE:
-      type_string = ".file";
-      break;
-    case HACKTREE_OBJECT_TYPE_META:
-      type_string = ".meta";
-      break;
-    default:
-      g_assert_not_reached ();
-    }
-  ret = g_strconcat (base_path, type_string, NULL);
-  g_free (base_path);
-  g_free (checksum_prefix);
-  return ret;
-}
-
-static char *
-prepare_dir_for_checksum_get_object_path (HacktreeRepo *self,
-                                          GChecksum    *checksum,
-                                          HacktreeObjectType type,
-                                          GError      **error)
-{
-  char *checksum_dir = NULL;
-  char *object_path = NULL;
-
-  object_path = get_object_path (self, g_checksum_get_string (checksum), type);
-  checksum_dir = g_path_get_dirname (object_path);
-
-  if (!ht_util_ensure_directory (checksum_dir, FALSE, error))
-    goto out;
-  
- out:
-  g_free (checksum_dir);
-  return object_path;
-}
-
-static gboolean
-link_one_file (HacktreeRepo *self, const char *path, HacktreeObjectType type,
-               gboolean ignore_exists, gboolean force,
-               GChecksum **out_checksum,
-               GError **error)
-{
-  char *src_basename = NULL;
-  char *src_dirname = NULL;
-  char *dest_basename = NULL;
-  char *tmp_dest_basename = NULL;
-  char *dest_dirname = NULL;
-  GChecksum *id = NULL;
-  DIR *src_dir = NULL;
-  DIR *dest_dir = NULL;
-  gboolean ret = FALSE;
-  struct stat stbuf;
-  char *dest_path = NULL;
-
-  src_basename = g_path_get_basename (path);
-  src_dirname = g_path_get_dirname (path);
-
-  src_dir = opendir (src_dirname);
-  if (src_dir == NULL)
-    {
-      ht_util_set_error_from_errno (error, errno);
-      goto out;
-    }
-
-  if (!hacktree_stat_and_checksum_file (dirfd (src_dir), path, &id, &stbuf, error))
-    goto out;
-  dest_path = prepare_dir_for_checksum_get_object_path (self, id, type, error);
-  if (!dest_path)
-    goto out;
-
-  dest_basename = g_path_get_basename (dest_path);
-  dest_dirname = g_path_get_dirname (dest_path);
-  dest_dir = opendir (dest_dirname);
-  if (dest_dir == NULL)
-    {
-      ht_util_set_error_from_errno (error, errno);
-      goto out;
-    }
-
-  if (force)
-    {
-      tmp_dest_basename = g_strconcat (dest_basename, ".tmp", NULL);
-      (void) unlinkat (dirfd (dest_dir), tmp_dest_basename, 0);
-    }
-  else
-    tmp_dest_basename = g_strdup (dest_basename);
-  
-  if (linkat (dirfd (src_dir), src_basename, dirfd (dest_dir), tmp_dest_basename, 0) < 0)
-    {
-      if (errno != EEXIST || !ignore_exists)
-        {
-          ht_util_set_error_from_errno (error, errno);
-          goto out;
-        }
-    }
-
-  if (force)
-    {
-      if (renameat (dirfd (dest_dir), tmp_dest_basename, 
-                    dirfd (dest_dir), dest_basename) < 0)
-        {
-          ht_util_set_error_from_errno (error, errno);
-          goto out;
-        }
-      (void) unlinkat (dirfd (dest_dir), tmp_dest_basename, 0);
-    }
-
-  *out_checksum = id;
-  id = NULL;
-  ret = TRUE;
- out:
-  if (id != NULL)
-    g_checksum_free (id);
-  if (src_dir != NULL)
-    closedir (src_dir);
-  if (dest_dir != NULL)
-    closedir (dest_dir);
-  g_free (src_basename);
-  g_free (src_dirname);
-  g_free (dest_basename);
-  g_free (tmp_dest_basename);
-  g_free (dest_dirname);
-  return ret;
-}
-
-gboolean
-hacktree_repo_link_file (HacktreeRepo *self,
-                         const char   *path,
-                         gboolean      ignore_exists,
-                         gboolean      force,
-                         GError      **error)
-{
-  HacktreeRepoPrivate *priv = GET_PRIVATE (self);
-  GChecksum *checksum = NULL;
-
-  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
-  g_return_val_if_fail (priv->inited, FALSE);
-
-  if (!link_one_file (self, path, HACKTREE_OBJECT_TYPE_FILE,
-                      ignore_exists, force, &checksum, error))
-    return FALSE;
-  g_checksum_free (checksum);
-  return TRUE;
-}
-
-typedef struct _ParsedTreeData ParsedTreeData;
-typedef struct _ParsedDirectoryData ParsedDirectoryData;
-
-static void parsed_tree_data_free (ParsedTreeData *pdata);
-
-struct _ParsedDirectoryData {
-  ParsedTreeData *tree_data;
-  char *metadata_sha256;
-  GVariant *meta_data;
-};
-
-static void
-parsed_directory_data_free (ParsedDirectoryData *pdata)
-{
-  if (pdata == NULL)
-    return;
-  parsed_tree_data_free (pdata->tree_data);
-  g_free (pdata->metadata_sha256);
-  g_variant_unref (pdata->meta_data);
-  g_free (pdata);
-}
-
-struct _ParsedTreeData {
-  GHashTable *files;  /* char* filename -> char* checksum */
-  GHashTable *directories;  /* char* dirname -> ParsedDirectoryData* */
-};
-
-static ParsedTreeData *
-parsed_tree_data_new (void)
-{
-  ParsedTreeData *ret = g_new0 (ParsedTreeData, 1);
-  ret->files = g_hash_table_new_full (g_str_hash, g_str_equal,
-                                      (GDestroyNotify)g_free, 
-                                      (GDestroyNotify)g_free);
-  ret->directories = g_hash_table_new_full (g_str_hash, g_str_equal,
-                                            (GDestroyNotify)g_free, 
-                                            (GDestroyNotify)parsed_directory_data_free);
-  return ret;
-}
-
-static void
-parsed_tree_data_free (ParsedTreeData *pdata)
-{
-  if (pdata == NULL)
-    return;
-  g_hash_table_destroy (pdata->files);
-  g_hash_table_destroy (pdata->directories);
-  g_free (pdata);
-}
-
-static gboolean
-parse_tree (HacktreeRepo    *self,
-            const char      *sha256,
-            ParsedTreeData **out_pdata,
-            GError         **error)
-{
-  gboolean ret = FALSE;
-  ParsedTreeData *ret_pdata = NULL;
-  int i, n;
-  guint32 version;
-  GVariant *tree_variant = NULL;
-  GVariant *meta_variant = NULL;
-  GVariant *files_variant = NULL;
-  GVariant *dirs_variant = NULL;
-
-  if (!load_gvariant_object (self, HACKTREE_SERIALIZED_TREE_VARIANT,
-                             sha256, &tree_variant, error))
-    goto out;
-
-  g_variant_get (tree_variant, "(u@a{sv}@a(ss)@a(sss))",
-                 &version, &meta_variant, &files_variant, &dirs_variant);
-
-  ret_pdata = parsed_tree_data_new ();
-  n = g_variant_n_children (files_variant);
-  for (i = 0; i < n; i++)
-    {
-      const char *filename;
-      const char *checksum;
-
-      g_variant_get_child (files_variant, i, "(ss)", &filename, &checksum);
-
-      g_hash_table_insert (ret_pdata->files, g_strdup (filename), g_strdup (checksum));
-    }
-
-  n = g_variant_n_children (dirs_variant);
-  for (i = 0; i < n; i++)
-    {
-      const char *dirname;
-      const char *tree_checksum;
-      const char *meta_checksum;
-      ParsedTreeData *child_tree = NULL;
-      GVariant *metadata = NULL;
-      ParsedDirectoryData *child_dir = NULL;
-
-      g_variant_get_child (dirs_variant, i, "(sss)",
-                           &dirname, &tree_checksum, &meta_checksum);
-      
-      if (!parse_tree (self, tree_checksum, &child_tree, error))
-        goto out;
-
-      if (!load_gvariant_object (self, HACKTREE_SERIALIZED_DIRMETA_VARIANT,
-                                 meta_checksum, &metadata, error))
-        {
-          parsed_tree_data_free (child_tree);
-          goto out;
-        }
-
-      child_dir = g_new0 (ParsedDirectoryData, 1);
-      child_dir->tree_data = child_tree;
-      child_dir->metadata_sha256 = g_strdup (meta_checksum);
-      child_dir->meta_data = g_variant_ref_sink (metadata);
-
-      g_hash_table_insert (ret_pdata->directories, g_strdup (dirname), child_dir);
-    }
-
-  ret = TRUE;
- out:
-  if (!ret)
-    parsed_tree_data_free (ret_pdata);
-  else
-    *out_pdata = ret_pdata;
-  if (tree_variant)
-    g_variant_unref (tree_variant);
-  if (meta_variant)
-    g_variant_unref (meta_variant);
-  if (files_variant)
-    g_variant_unref (files_variant);
-  if (dirs_variant)
-    g_variant_unref (dirs_variant);
-  return ret;
-}
-
-static gboolean
-load_commit_and_trees (HacktreeRepo   *self,
-                       const char     *commit_sha256,
-                       GVariant      **out_commit,
-                       ParsedDirectoryData **out_root_data,
-                       GError        **error)
-{
-  HacktreeRepoPrivate *priv = GET_PRIVATE (self);
-  GVariant *ret_commit = NULL;
-  ParsedDirectoryData *ret_root_data = NULL;
-  ParsedTreeData *tree_data = NULL;
-  char *ret_metadata_checksum = NULL;
-  GVariant *root_metadata = NULL;
-  gboolean ret = FALSE;
-  const char *tree_contents_checksum;
-  const char *tree_meta_checksum;
-
-  if (!priv->current_head)
-    {
-      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                           "Can't load current commit; no HEAD reference");
-      goto out;
-    }
-
-  if (!load_gvariant_object (self, HACKTREE_SERIALIZED_COMMIT_VARIANT,
-                             commit_sha256, &ret_commit, error))
-    goto out;
-
-  g_variant_get_child (ret_commit, 6, "&s", &tree_contents_checksum);
-  g_variant_get_child (ret_commit, 7, "&s", &tree_meta_checksum);
-
-  if (!load_gvariant_object (self, HACKTREE_SERIALIZED_DIRMETA_VARIANT,
-                             tree_meta_checksum, &root_metadata, error))
-    goto out;
-
-  if (!parse_tree (self, tree_contents_checksum, &tree_data, error))
-    goto out;
-
-  ret_root_data = g_new0 (ParsedDirectoryData, 1);
-  ret_root_data->tree_data = tree_data;
-  ret_root_data->metadata_sha256 = g_strdup (tree_meta_checksum);
-  ret_root_data->meta_data = root_metadata;
-  root_metadata = NULL;
-
-  ret = TRUE;
-  *out_commit = ret_commit;
-  ret_commit = NULL;
-  *out_root_data = ret_root_data;
-  ret_root_data = NULL;
- out:
-  if (ret_commit)
-    g_variant_unref (ret_commit);
-  parsed_directory_data_free (ret_root_data);
-  g_free (ret_metadata_checksum);
-  if (root_metadata)
-    g_variant_unref (root_metadata);
-  return ret;
-}
-
-static GVariant *
-create_empty_gvariant_dict (void)
-{
-  GVariantBuilder builder;
-  g_variant_builder_init (&builder, G_VARIANT_TYPE("a{sv}"));
-  return g_variant_builder_end (&builder);
-}
-
-static gboolean
-import_parsed_tree (HacktreeRepo    *self,
-                    ParsedTreeData  *tree,
-                    GChecksum      **out_checksum,
-                    GError         **error)
-{
-  gboolean ret = FALSE;
-  GVariant *serialized_tree = NULL;
-  gboolean builders_initialized = FALSE;
-  GVariantBuilder files_builder;
-  GVariantBuilder dirs_builder;
-  GHashTableIter hash_iter;
-  gpointer key, value;
-
-  g_variant_builder_init (&files_builder, G_VARIANT_TYPE ("a(ss)"));
-  g_variant_builder_init (&dirs_builder, G_VARIANT_TYPE ("a(sss)"));
-  builders_initialized = TRUE;
-
-  g_hash_table_iter_init (&hash_iter, tree->files);
-  while (g_hash_table_iter_next (&hash_iter, &key, &value))
-    {
-      const char *name = key;
-      const char *checksum = value;
-
-      g_variant_builder_add (&files_builder, "(ss)", name, checksum);
-    }
-
-  g_hash_table_iter_init (&hash_iter, tree->directories);
-  while (g_hash_table_iter_next (&hash_iter, &key, &value))
-    {
-      const char *name = key;
-      GChecksum *dir_checksum = NULL;
-      ParsedDirectoryData *dir = value;
-
-      if (!import_parsed_tree (self, dir->tree_data, &dir_checksum, error))
-        goto out;
-
-      g_variant_builder_add (&dirs_builder, "(sss)",
-                             name, g_checksum_get_string (dir_checksum), dir->metadata_sha256);
-    }
-
-  serialized_tree = g_variant_new ("(u@a{sv}@a(ss)@a(sss))",
-                                   0,
-                                   create_empty_gvariant_dict (),
-                                   g_variant_builder_end (&files_builder),
-                                   g_variant_builder_end (&dirs_builder));
-  builders_initialized = FALSE;
-  g_variant_ref_sink (serialized_tree);
-  if (!import_gvariant_object (self, HACKTREE_SERIALIZED_TREE_VARIANT, serialized_tree, out_checksum, error))
-    goto out;
-  
-  ret = TRUE;
- out:
-  if (builders_initialized)
-    {
-      g_variant_builder_clear (&files_builder);
-      g_variant_builder_clear (&dirs_builder);
-    }
-  if (serialized_tree)
-    g_variant_unref (serialized_tree);
-  return ret;
-}
-
-static gboolean
-check_path (const char *filename,
-            GError    **error)
-{
-  gboolean ret = FALSE;
-
-  if (!*filename)
-    {
-      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                   "Invalid empty filename");
-      goto out;
-    }
-
-  if (strcmp (filename, ".") == 0)
-    {
-      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                   "Self-reference '.' in filename '%s' not allowed (yet)", filename);
-      goto out;
-    }
-  
-  if (ht_util_filename_has_dotdot (filename))
-    {
-      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                   "Path uplink '..' in filename '%s' not allowed (yet)", filename);
-      goto out;
-    }
-  
-  if (g_path_is_absolute (filename))
-    {
-      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                   "Absolute filename '%s' not allowed (yet)", filename);
-      goto out;
-    }
-
-  ret = TRUE;
- out:
-  return ret;
-}
-
-static gboolean
-walk_parsed_tree (HacktreeRepo  *self,
-                  const char    *filename,
-                  ParsedTreeData *tree,
-                  int            *out_filename_index, /* out*/
-                  char          **out_component, /* out, must free */
-                  ParsedTreeData **out_tree, /* out, but do not free */
-                  GError        **error)
-{
-  gboolean ret = FALSE;
-  GPtrArray *components = NULL;
-  ParsedTreeData *current_tree = tree;
-  const char *component = NULL;
-  const char *file_sha1 = NULL;
-  ParsedDirectoryData *dir = NULL;
-  int i;
-  int ret_filename_index = 0;
-
-  components = ht_util_path_split (filename);
-  g_assert (components != NULL);
-
-  current_tree = tree;
-  for (i = 0; i < components->len - 1; i++)
-    {
-      component = components->pdata[i];
-      file_sha1 = g_hash_table_lookup (current_tree->files, component);
-      dir = g_hash_table_lookup (current_tree->directories, component);
-
-      if (!(file_sha1 || dir))
-        {
-          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                       "No such file or directory: %s",
-                       filename);
-          goto out;
-        }
-      else if (file_sha1)
-        {
-          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                       "Encountered non-directory '%s' in '%s'",
-                       (char*)component,
-                       filename);
-          goto out;
-        }
-      else
-        {
-          g_assert (dir != NULL);
-          current_tree = dir->tree_data;
-          ret_filename_index++;
-        }
-    }
-
-  ret = TRUE;
-  *out_filename_index = i;
-  *out_component = components->pdata[components->len-1];
-  components->pdata[components->len-1] = NULL; /* steal */
-  *out_tree = current_tree;
- out:
-  g_ptr_array_free (components, TRUE);
-  return ret;
-}
-
-static gboolean
-remove_files_from_tree (HacktreeRepo   *self,
-                        const char     *base,
-                        GPtrArray      *removed_files,
-                        ParsedTreeData *tree,
-                        GError        **error)
-{
-  gboolean ret = FALSE;
-  int i;
-
-  for (i = 0; i < removed_files->len; i++)
-    {
-      const char *filename = removed_files->pdata[i];
-      int filename_index;
-      char *component = NULL;
-      ParsedTreeData *parent;
-      const char *file_sha1;
-      ParsedTreeData *dir;
-
-      if (!check_path (filename, error))
-        goto out;
-       
-      if (!walk_parsed_tree (self, filename, tree,
-                             &filename_index, (char**)&component, &parent,
-                             error))
-        goto out;
-
-      file_sha1 = g_hash_table_lookup (parent->files, component);
-      dir = g_hash_table_lookup (parent->directories, component);
-
-      if (file_sha1)
-        g_hash_table_remove (parent->files, component);
-      else if (dir)
-        g_hash_table_remove (parent->directories, component);
-      else
-        {
-          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                       "No such file or directory: %s",
-                       filename);
-          g_free (component);
-          goto out;
-        }
-      g_free (component);
-    }
-  
-  ret = TRUE;
- out:
-  return ret;
-}
-
-static gboolean
-add_one_directory_to_tree_and_import (HacktreeRepo   *self,
-                                      const char     *basename,
-                                      const char     *abspath,
-                                      ParsedTreeData *tree,
-                                      ParsedDirectoryData **dir, /*inout*/
-                                      GError        **error)
-{
-  gboolean ret = FALSE;
-  GVariant *dirmeta = NULL;
-  GChecksum *dir_meta_checksum = NULL;
-  ParsedDirectoryData *dir_value = *dir;
-  
-  g_assert (tree != NULL);
-
-  if (!import_directory_meta (self, abspath, &dirmeta, &dir_meta_checksum, error))
-    goto out;
-
-  if (dir_value)
-    {
-      g_variant_unref (dir_value->meta_data);
-      dir_value->meta_data = dirmeta;
-    }
-  else
-    {
-      dir_value = g_new0 (ParsedDirectoryData, 1);
-      dir_value->tree_data = parsed_tree_data_new ();
-      dir_value->metadata_sha256 = g_strdup (g_checksum_get_string (dir_meta_checksum));
-      dir_value->meta_data = dirmeta;
-      g_hash_table_insert (tree->directories, g_strdup (basename), dir_value);
-    }
-
-  ret = TRUE;
-  *dir = dir_value;
- out:
-  if (dir_meta_checksum)
-    g_checksum_free (dir_meta_checksum);
-  return ret;
-}
-
-static gboolean
-add_one_file_to_tree_and_import (HacktreeRepo   *self,
-                                 const char     *basename,
-                                 const char     *abspath,
-                                 ParsedTreeData *tree,
-                                 GError        **error)
-{
-  gboolean ret = FALSE;
-  GChecksum *checksum = NULL;
-  
-  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
-  g_assert (tree != NULL);
-
-  if (!link_one_file (self, abspath, HACKTREE_OBJECT_TYPE_FILE,
-                      TRUE, FALSE, &checksum, error))
-    goto out;
-
-  g_hash_table_replace (tree->files, g_strdup (basename),
-                        g_strdup (g_checksum_get_string (checksum)));
-
-  ret = TRUE;
- out:
-  if (checksum)
-    g_checksum_free (checksum);
-  return ret;
-}
-
-static gboolean
-add_one_path_to_tree_and_import (HacktreeRepo   *self,
-                                 const char     *base,
-                                 const char     *filename,
-                                 ParsedTreeData *tree,
-                                 GError        **error)
-{
-  gboolean ret = FALSE;
-  GPtrArray *components = NULL;
-  struct stat stbuf;
-  char *component_abspath = NULL;
-  ParsedTreeData *current_tree = tree;
-  const char *component = NULL;
-  const char *file_sha1;
-  char *abspath = NULL;
-  ParsedDirectoryData *dir;
-  int i;
-  gboolean is_directory;
-
-  if (!check_path (filename, error))
-    goto out;
-
-  abspath = g_build_filename (base, filename, NULL);
-
-  if (lstat (abspath, &stbuf) < 0)
-    {
-      ht_util_set_error_from_errno (error, errno);
-      goto out;
-    }
-  is_directory = S_ISDIR(stbuf.st_mode);
-       
-  if (components)
-    g_ptr_array_free (components, TRUE);
-  components = ht_util_path_split (filename);
-  g_assert (components->len > 0);
-
-  current_tree = tree;
-  for (i = 0; i < components->len; i++)
-    {
-      component = components->pdata[i];
-      g_free (component_abspath);
-      component_abspath = ht_util_path_join_n (base, components, i);
-      file_sha1 = g_hash_table_lookup (current_tree->files, component);
-      dir = g_hash_table_lookup (current_tree->directories, component);
-
-      g_assert_cmpstr (component, !=, ".");
-
-      if (i < components->len - 1)
-        {
-          if (file_sha1 != NULL)
-            {
-              g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                           "Encountered non-directory '%s' in '%s'",
-                           component,
-                           filename);
-              goto out;
-            }
-          /* Implicitly add intermediate directories */
-          if (!add_one_directory_to_tree_and_import (self, component,
-                                                     component_abspath, current_tree, &dir,
-                                                     error))
-            goto out;
-          g_assert (dir != NULL);
-          current_tree = dir->tree_data;
-        }
-      else if (is_directory)
-        {
-          if (file_sha1 != NULL)
-            {
-              g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                           "File '%s' can't be overwritten by directory",
-                           filename);
-              goto out;
-            }
-          if (!add_one_directory_to_tree_and_import (self, component,
-                                                     abspath, current_tree, &dir,
-                                                     error))
-            goto out;
-        }
-      else 
-        {
-          g_assert (!is_directory);
-          if (dir != NULL)
-            {
-              g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                           "File '%s' can't be overwritten by directory",
-                           filename);
-              goto out;
-            }
-          if (!add_one_file_to_tree_and_import (self, component, abspath,
-                                                current_tree, error))
-            goto out;
-        }
-    }
-
-  ret = TRUE;
- out:
-  g_free (component_abspath);
-  g_free (abspath);
-  return ret;
-}
-
-static gboolean
-add_files_to_tree_and_import (HacktreeRepo   *self,
-                              const char     *base,
-                              GPtrArray      *added_files,
-                              ParsedTreeData *tree,
-                              GError        **error)
-{
-  gboolean ret = FALSE;
-  int i;
-
-  for (i = 0; i < added_files->len; i++)
-    {
-      const char *path = added_files->pdata[i];
-
-      if (!add_one_path_to_tree_and_import (self, base, path, tree, error))
-        goto out;
-    }
-  
-  ret = TRUE;
- out:
-  return ret;
-}
-
-static gboolean
-commit_parsed_tree (HacktreeRepo *self,
-                    const char   *subject,
-                    const char   *body,
-                    GVariant     *metadata,
-                    ParsedDirectoryData *root,
-                    GChecksum   **out_commit,
-                    GError      **error)
-{
-  HacktreeRepoPrivate *priv = GET_PRIVATE (self);
-  gboolean ret = FALSE;
-  GChecksum *root_checksum = NULL;
-  GChecksum *ret_commit = NULL;
-  GVariant *commit = NULL;
-  GDateTime *now = NULL;
-
-  if (!import_parsed_tree (self, root->tree_data, &root_checksum, error))
-    goto out;
-
-  now = g_date_time_new_now_utc ();
-  commit = g_variant_new ("(u@a{sv}ssstss)",
-                          HACKTREE_COMMIT_VERSION,
-                          create_empty_gvariant_dict (),
-                          priv->current_head ? priv->current_head : "",
-                          subject, body ? body : "",
-                          g_date_time_to_unix (now) / G_TIME_SPAN_SECOND,
-                          g_checksum_get_string (root_checksum),
-                          root->metadata_sha256);
-  if (!import_gvariant_object (self, HACKTREE_SERIALIZED_COMMIT_VARIANT,
-                               commit, &ret_commit, error))
-    goto out;
-
-  if (!write_checksum_file (priv->head_ref_path, g_checksum_get_string (ret_commit), error))
-    goto out;
-
-  g_free (priv->current_head);
-  priv->current_head = g_strdup (g_checksum_get_string (ret_commit));
-
-  ret = TRUE;
-  *out_commit = ret_commit;
- out:
-  if (root_checksum)
-    g_checksum_free (root_checksum);
-  if (commit)
-    g_variant_unref (commit);
-  if (now)
-    g_date_time_unref (now);
-  return ret;
-}
-
-static gboolean
-import_root (HacktreeRepo     *self,
-             const char        *base,
-             ParsedDirectoryData **out_root,
-             GError              **error)
-{
-  gboolean ret = FALSE;
-  ParsedDirectoryData *ret_root = NULL;
-  GVariant *root_metadata = NULL;
-  GChecksum *root_meta_checksum = NULL;
-
-  if (!import_directory_meta (self, base, &root_metadata, &root_meta_checksum, error))
-    goto out;
-
-  ret_root = g_new0 (ParsedDirectoryData, 1);
-  ret_root->tree_data = parsed_tree_data_new ();
-  ret_root->meta_data = root_metadata;
-  root_metadata = NULL;
-  ret_root->metadata_sha256 = g_strdup (g_checksum_get_string (root_meta_checksum));
-
-  ret = TRUE;
-  *out_root = ret_root;
-  ret_root = NULL;
- out:
-  if (root_metadata)
-    g_variant_unref (root_metadata);
-  if (root_meta_checksum)
-    g_checksum_free (root_meta_checksum);
-  parsed_directory_data_free (ret_root);
-  return ret;
-}
-
-gboolean
-hacktree_repo_commit (HacktreeRepo *self,
-                      const char   *subject,
-                      const char   *body,
-                      GVariant     *metadata,
-                      const char   *base,
-                      GPtrArray    *modified_files,
-                      GPtrArray    *removed_files,
-                      GChecksum   **out_commit,
-                      GError      **error)
-{
-  HacktreeRepoPrivate *priv = GET_PRIVATE (self);
-  gboolean ret = FALSE;
-  ParsedDirectoryData *root = NULL;
-  GVariant *previous_commit = NULL;
-  GChecksum *ret_commit_checksum = NULL;
-  char *orig_root_metadata_sha256 = NULL;
-  GVariant *root_metadata = NULL;
-  GChecksum *root_meta_checksum = NULL;
-
-  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
-  g_return_val_if_fail (priv->inited, FALSE);
-
-  if (priv->current_head)
-    {
-      if (!load_commit_and_trees (self, priv->current_head, &previous_commit, &root, error))
-        goto out;
-      if (!import_directory_meta (self, base, &root_metadata, &root_meta_checksum, error))
-        goto out;
-      g_variant_unref (root->meta_data);
-      root->meta_data = root_metadata;
-      root_metadata = NULL;
-      root->metadata_sha256 = g_strdup (g_checksum_get_string (root_meta_checksum));
-    }
-  else
-    {
-      /* Initial commit */
-      if (!import_root (self, base, &root, error))
-        goto out;
-    }
-
-  if (!remove_files_from_tree (self, base, removed_files, root->tree_data, error))
-    goto out;
-
-  if (!add_files_to_tree_and_import (self, base, modified_files, root->tree_data, error))
-    goto out;
-
-  if (!commit_parsed_tree (self, subject, body, metadata, root,
-                           &ret_commit_checksum, error))
-    goto out;
-  
-  ret = TRUE;
- out:
-  if (!ret)
-    {
-      if (ret_commit_checksum)
-        g_checksum_free (ret_commit_checksum);
-    }
-  else
-    {
-      *out_commit = ret_commit_checksum;
-    }
-  if (previous_commit)
-    g_variant_unref (previous_commit);
-  parsed_directory_data_free (root);
-  if (root_metadata)
-    g_variant_unref (root_metadata);
-  if (root_meta_checksum)
-    g_checksum_free (root_meta_checksum);
-  return ret;
-}
-
-gboolean      
-hacktree_repo_commit_from_filelist_fd (HacktreeRepo *self,
-                                       const char   *subject,
-                                       const char   *body,
-                                       GVariant     *metadata,
-                                       const char   *base,
-                                       int           fd,
-                                       char          separator,
-                                       GChecksum   **out_commit,
-                                       GError      **error)
-{
-  HacktreeRepoPrivate *priv = GET_PRIVATE (self);
-  gboolean ret = FALSE;
-  ParsedDirectoryData *root = NULL;
-  GVariant *previous_commit = NULL;
-  GChecksum *ret_commit_checksum = NULL;
-  GUnixInputStream *in = NULL;
-  GDataInputStream *datain = NULL;
-  char *filename = NULL;
-  gsize filename_len;
-  GError *temp_error = NULL;
-  GVariant *root_metadata = NULL;
-  GChecksum *root_meta_checksum = NULL;
-
-  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
-  g_return_val_if_fail (priv->inited, FALSE);
-
-  /* We're overwriting the tree */
-  if (!import_root (self, base, &root, error))
-    goto out;
-
-  in = (GUnixInputStream*)g_unix_input_stream_new (fd, FALSE);
-  datain = g_data_input_stream_new ((GInputStream*)in);
-
-  while ((filename = g_data_input_stream_read_upto (datain, &separator, 1,
-                                                    &filename_len, NULL, &temp_error)) != NULL)
-    {
-      if (!g_data_input_stream_read_byte (datain, NULL, &temp_error))
-        {
-          if (temp_error != NULL)
-            {
-              g_propagate_prefixed_error (error, temp_error, "%s", "While reading filelist: ");
-              goto out;
-            }
-        }
-      if (!add_one_path_to_tree_and_import (self, base, filename, root->tree_data, error))
-        goto out;
-      g_free (filename);
-      filename = NULL;
-    }
-  if (filename == NULL && temp_error != NULL)
-    {
-      g_propagate_prefixed_error (error, temp_error, "%s", "While reading filelist: ");
-      goto out;
-    }
-  if (!commit_parsed_tree (self, subject, body, metadata,
-                           root, &ret_commit_checksum, error))
-    goto out;
-  
-  ret = TRUE;
- out:
-  if (!ret)
-    {
-      if (ret_commit_checksum)
-        g_checksum_free (ret_commit_checksum);
-    }
-  else
-    {
-      *out_commit = ret_commit_checksum;
-    }
-  if (root_metadata)
-    g_variant_unref (root_metadata);
-  if (root_meta_checksum)
-    g_checksum_free (root_meta_checksum);
-  g_clear_object (&datain);
-  g_clear_object (&in);
-  g_free (filename);
-  parsed_directory_data_free (root);
-  return ret;
-  
-}
-
-static gboolean
-iter_object_dir (HacktreeRepo   *self,
-                 GFile          *dir,
-                 HacktreeRepoObjectIter  callback,
-                 gpointer                user_data,
-                 GError                **error)
-{
-  gboolean ret = FALSE;
-  GError *temp_error = NULL;
-  GFileEnumerator *enumerator = NULL;
-  GFileInfo *file_info = NULL;
-  char *dirpath = NULL;
-
-  dirpath = g_file_get_path (dir);
-
-  enumerator = g_file_enumerate_children (dir, "standard::name,standard::type,unix::*", 
-                                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
-                                          NULL, 
-                                          error);
-  if (!enumerator)
-    goto out;
-  
-  while ((file_info = g_file_enumerator_next_file (enumerator, NULL, &temp_error)) != NULL)
-    {
-      const char *name;
-      guint32 type;
-      name = g_file_info_get_attribute_byte_string (file_info, "standard::name"); 
-      type = g_file_info_get_attribute_uint32 (file_info, "standard::type");
-      
-      if (type != G_FILE_TYPE_DIRECTORY
-          && (g_str_has_suffix (name, ".meta")
-              || g_str_has_suffix (name, ".file")))
-        {
-          char *dot;
-          char *path;
-          
-          dot = strrchr (name, '.');
-          g_assert (dot);
-          
-          if ((dot - name) == 62)
-            {
-              path = g_build_filename (dirpath, name, NULL);
-              callback (self, path, file_info, user_data);
-              g_free (path);
-            }
-        }
-
-      g_object_unref (file_info);
-    }
-  if (file_info == NULL && temp_error != NULL)
-    {
-      g_propagate_error (error, temp_error);
-      goto out;
-    }
-  if (!g_file_enumerator_close (enumerator, NULL, error))
-    goto out;
-
-  ret = TRUE;
- out:
-  g_free (dirpath);
-  return ret;
-}
-
-gboolean
-hacktree_repo_iter_objects (HacktreeRepo  *self,
-                            HacktreeRepoObjectIter callback,
-                            gpointer       user_data,
-                            GError        **error)
-{
-  HacktreeRepoPrivate *priv = GET_PRIVATE (self);
-  GFile *objectdir = NULL;
-  GFileEnumerator *enumerator = NULL;
-  gboolean ret = FALSE;
-  GFileInfo *file_info = NULL;
-  GError *temp_error = NULL;
-
-  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
-  g_return_val_if_fail (priv->inited, FALSE);
-
-  objectdir = ht_util_new_file_for_path (priv->objects_path);
-  enumerator = g_file_enumerate_children (objectdir, "standard::name,standard::type,unix::*", 
-                                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
-                                          NULL, 
-                                          error);
-  if (!enumerator)
-    goto out;
-
-  while ((file_info = g_file_enumerator_next_file (enumerator, NULL, &temp_error)) != NULL)
-    {
-      const char *name;
-      guint32 type;
-
-      name = g_file_info_get_attribute_byte_string (file_info, "standard::name"); 
-      type = g_file_info_get_attribute_uint32 (file_info, "standard::type");
-      
-      if (strlen (name) == 2 && type == G_FILE_TYPE_DIRECTORY)
-        {
-          GFile *objdir = g_file_get_child (objectdir, name);
-          if (!iter_object_dir (self, objdir, callback, user_data, error))
-            {
-              g_object_unref (objdir);
-              goto out;
-            }
-          g_object_unref (objdir);
-        }
-      g_object_unref (file_info);
-    }
-  if (file_info == NULL && temp_error != NULL)
-    {
-      g_propagate_error (error, temp_error);
-      goto out;
-    }
-  if (!g_file_enumerator_close (enumerator, NULL, error))
-    goto out;
-
-  ret = TRUE;
- out:
-  g_clear_object (&file_info);
-  g_clear_object (&enumerator);
-  g_clear_object (&objectdir);
-  return ret;
-}
-
-gboolean
-hacktree_repo_load_variant (HacktreeRepo *repo,
-                            const char   *sha256,
-                            HacktreeSerializedVariantType *out_type,
-                            GVariant    **out_variant,
-                            GError      **error)
-{
-  gboolean ret = FALSE;
-  HacktreeSerializedVariantType ret_type;
-  GVariant *ret_variant = NULL;
-
-  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
-  
-  if (!load_gvariant_object_unknown (repo, sha256, &ret_type, &ret_variant, error))
-    goto out;
-
-  ret = TRUE;
-  *out_type = ret_type;
-  *out_variant = ret_variant;
- out:
-  if (!ret)
-    {
-      if (ret_variant)
-        g_variant_unref (ret_variant);
-      g_prefix_error (error, "Failed to load metadata variant '%s': ", sha256);
-    }
-  return ret;
-}
-
-const char *
-hacktree_repo_get_head (HacktreeRepo  *self)
-{
-  HacktreeRepoPrivate *priv = GET_PRIVATE (self);
-
-  g_return_val_if_fail (priv->inited, NULL);
-
-  return priv->current_head;
-}
-
-static gboolean
-resolve_ref (HacktreeRepo *self,
-             const char   *ref,
-             char       **resolved,
-             GError      **error)
-{
-  if (strcmp (ref, "HEAD") == 0)
-    {
-      *resolved = g_strdup (hacktree_repo_get_head (self));
-      return TRUE;
-    }
-  else if (strlen (ref) == 64)
-    {
-      *resolved = g_strdup (ref);
-      return TRUE;
-    }
-  g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-               "Invalid ref '%s' (must be SHA256 or HEAD)", ref);
-  return FALSE;
-}
-
-static gboolean
-checkout_tree (HacktreeRepo    *self,
-               ParsedTreeData  *tree,
-               const char      *destination,
-               GError         **error);
-
-static gboolean
-checkout_one_directory (HacktreeRepo  *self,
-                        const char *destination,
-                        const char *dirname,
-                        ParsedDirectoryData *dir,
-                        GError         **error)
-{
-  gboolean ret = FALSE;
-  char *dest_path = NULL;
-  guint32 version, uid, gid, mode;
-  GVariant *xattr_variant = NULL;
-  const guint8 *xattrs = NULL;
-  gsize xattr_len;
-
-  dest_path = g_build_filename (destination, dirname, NULL);
-      
-  g_variant_get (dir->meta_data, "(uuuu@a(ayay))",
-                 &version, &uid, &gid, &mode,
-                 &xattr_variant);
-
-  if (mkdir (dest_path, (mode_t)mode) < 0)
-    {
-      ht_util_set_error_from_errno (error, errno);
-      g_prefix_error (error, "Failed to create directory '%s': ", dest_path);
-      goto out;
-    }
-      
-  if (!checkout_tree (self, dir->tree_data, dest_path, error))
-    goto out;
-
-  /* TODO - xattrs */
-      
-  ret = TRUE;
- out:
-  g_free (dest_path);
-  g_variant_unref (xattr_variant);
-  return ret;
-}
-
-static gboolean
-checkout_tree (HacktreeRepo    *self,
-               ParsedTreeData  *tree,
-               const char      *destination,
-               GError         **error)
-{
-  gboolean ret = FALSE;
-  GHashTableIter hash_iter;
-  gpointer key, value;
-
-  g_hash_table_iter_init (&hash_iter, tree->files);
-  while (g_hash_table_iter_next (&hash_iter, &key, &value))
-    {
-      const char *filename = key;
-      const char *checksum = value;
-      char *object_path;
-      char *dest_path;
-
-      object_path = get_object_path (self, checksum, HACKTREE_OBJECT_TYPE_FILE);
-      dest_path = g_build_filename (destination, filename, NULL);
-      if (link (object_path, dest_path) < 0)
-        {
-          ht_util_set_error_from_errno (error, errno);
-          g_free (object_path);
-          g_free (dest_path);
-          goto out;
-        }
-      g_free (object_path);
-      g_free (dest_path);
-    }
-
-  g_hash_table_iter_init (&hash_iter, tree->directories);
-  while (g_hash_table_iter_next (&hash_iter, &key, &value))
-    {
-      const char *dirname = key;
-      ParsedDirectoryData *dir = value;
-      
-      if (!checkout_one_directory (self, destination, dirname, dir, error))
-        goto out;
-    }
-
-  ret = TRUE;
- out:
-  return ret;
-}
-
-gboolean
-hacktree_repo_checkout (HacktreeRepo *self,
-                        const char   *ref,
-                        const char   *destination,
-                        GError      **error)
-{
-  gboolean ret = FALSE;
-  GVariant *commit = NULL;
-  char *resolved = NULL;
-  char *root_meta_sha = NULL;
-  ParsedDirectoryData *root = NULL;
-
-  if (g_file_test (destination, G_FILE_TEST_EXISTS))
-    {
-      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                   "Destination path '%s' already exists",
-                   destination);
-      goto out;
-    }
-
-  if (!resolve_ref (self, ref, &resolved, error))
-    goto out;
-
-  if (!load_commit_and_trees (self, resolved, &commit, &root, error))
-    goto out;
-
-  if (!checkout_one_directory (self, destination, NULL, root, error))
-    goto out;
-
-  ret = TRUE;
- out:
-  g_free (resolved);
-  if (commit)
-    g_variant_unref (commit);
-  parsed_directory_data_free (root);
-  g_free (root_meta_sha);
-  return ret;
-}
diff --git a/src/libhacktree/hacktree-repo.h b/src/libhacktree/hacktree-repo.h
deleted file mode 100644 (file)
index 2f6d703..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-/* hacktree-repo.h */
-
-#ifndef _HACKTREE_REPO
-#define _HACKTREE_REPO
-
-#include <glib-object.h>
-
-G_BEGIN_DECLS
-
-#define HACKTREE_TYPE_REPO hacktree_repo_get_type()
-#define HACKTREE_REPO(obj) \
-  (G_TYPE_CHECK_INSTANCE_CAST ((obj), HACKTREE_TYPE_REPO, HacktreeRepo))
-#define HACKTREE_REPO_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_CAST ((klass), HACKTREE_TYPE_REPO, HacktreeRepoClass))
-#define HACKTREE_IS_REPO(obj) \
-  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), HACKTREE_TYPE_REPO))
-#define HACKTREE_IS_REPO_CLASS(klass) \
-  (G_TYPE_CHECK_CLASS_TYPE ((klass), HACKTREE_TYPE_REPO))
-#define HACKTREE_REPO_GET_CLASS(obj) \
-  (G_TYPE_INSTANCE_GET_CLASS ((obj), HACKTREE_TYPE_REPO, HacktreeRepoClass))
-
-typedef struct {
-  GObject parent;
-} HacktreeRepo;
-
-typedef struct {
-  GObjectClass parent_class;
-} HacktreeRepoClass;
-
-GType hacktree_repo_get_type (void);
-
-HacktreeRepo* hacktree_repo_new (const char *path);
-
-gboolean      hacktree_repo_check (HacktreeRepo  *self, GError **error);
-
-gboolean      hacktree_repo_link_file (HacktreeRepo *self,
-                                       const char   *path,
-                                       gboolean      ignore_exists,
-                                       gboolean      force,
-                                       GError      **error);
-
-const char *  hacktree_repo_get_head (HacktreeRepo  *self);
-
-gboolean      hacktree_repo_load_variant (HacktreeRepo *self,
-                                          const char   *sha256,
-                                          HacktreeSerializedVariantType *out_type,
-                                          GVariant    **out_variant,
-                                          GError      **error);
-
-gboolean      hacktree_repo_commit (HacktreeRepo *self,
-                                    const char   *subject,
-                                    const char   *body,
-                                    GVariant     *metadata,
-                                    const char   *base,
-                                    GPtrArray    *modified_files,
-                                    GPtrArray    *removed_files,
-                                    GChecksum   **out_commit,
-                                    GError      **error);
-
-gboolean      hacktree_repo_commit_from_filelist_fd (HacktreeRepo *self,
-                                                     const char   *subject,
-                                                     const char   *body,
-                                                     GVariant     *metadata,
-                                                     const char   *base,
-                                                     int           fd,
-                                                     char          separator,
-                                                     GChecksum   **out_commit,
-                                                     GError      **error);
-
-gboolean      hacktree_repo_checkout (HacktreeRepo *self,
-                                      const char   *ref,
-                                      const char   *destination,
-                                      GError      **error);
-
-typedef void (*HacktreeRepoObjectIter) (HacktreeRepo *self, const char *path,
-                                        GFileInfo *fileinfo, gpointer user_data);
-
-gboolean     hacktree_repo_iter_objects (HacktreeRepo  *self,
-                                         HacktreeRepoObjectIter callback,
-                                         gpointer       user_data,
-                                         GError        **error);
-
-G_END_DECLS
-
-#endif /* _HACKTREE_REPO */
diff --git a/src/libhacktree/hacktree-types.h b/src/libhacktree/hacktree-types.h
deleted file mode 100644 (file)
index 0824ad4..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#ifndef __HACKTREE_TYPES_H__
-#define __HACKTREE_TYPES_H__
-
-#include <gio/gio.h>
-
-G_BEGIN_DECLS
-
-#define HACKTREE_REPO_DIR ".ht"
-
-G_END_DECLS
-
-#endif
diff --git a/src/libhacktree/hacktree.h b/src/libhacktree/hacktree.h
deleted file mode 100644 (file)
index 0cf2f15..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#ifndef __HACKTREE_H__
-
-#include <hacktree-core.h>
-#include <hacktree-repo.h>
-#include <hacktree-types.h>
-
-#endif
diff --git a/src/libhtutil/ht-gio-utils.c b/src/libhtutil/ht-gio-utils.c
deleted file mode 100644 (file)
index c49b883..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include "config.h"
-
-#include <glib-unix.h>
-#include <gio/gio.h>
-#include <gio/gunixinputstream.h>
-
-#include <string.h>
-
-#include "htutil.h"
-
-gboolean
-ht_util_ensure_directory (const char *path, gboolean with_parents, GError **error)
-{
-  GFile *dir;
-  GError *temp_error = NULL;
-  gboolean ret = FALSE;
-
-  dir = g_file_new_for_path (path);
-  if (with_parents)
-    ret = g_file_make_directory_with_parents (dir, NULL, &temp_error);
-  else
-    ret = g_file_make_directory (dir, NULL, &temp_error);
-  if (!ret)
-    {
-      if (!g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_EXISTS))
-        {
-          g_propagate_error (error, temp_error);
-          goto out;
-        }
-      else
-        g_clear_error (&temp_error);
-    }
-
-  ret = TRUE;
- out:
-  g_clear_object (&dir);
-  return ret;
-}
-
-
-char *
-ht_util_get_file_contents_utf8 (const char *path,
-                                GError    **error)
-{
-  char *contents;
-  gsize len;
-  if (!g_file_get_contents (path, &contents, &len, error))
-    return NULL;
-  if (!g_utf8_validate (contents, len, NULL))
-    {
-      g_free (contents);
-      g_set_error (error,
-                   G_IO_ERROR,
-                   G_IO_ERROR_FAILED,
-                   "File %s contains invalid UTF-8",
-                   path);
-      return NULL;
-    }
-  return contents;
-}
-
-GInputStream *
-ht_util_read_file_noatime (GFile *file, GCancellable *cancellable, GError **error)
-{
-  GInputStream *ret = NULL;
-  int fd;
-  int flags = O_RDONLY;
-  char *path = NULL;
-
-  path = g_file_get_path (file);
-#ifdef O_NOATIME
-  flags |= O_NOATIME;
-#endif
-  fd = open (path, flags);
-  if (fd < 0)
-    {
-      ht_util_set_error_from_errno (error, errno);
-      goto out;
-    }
-
-  ret = (GInputStream*)g_unix_input_stream_new (fd, TRUE);
-  
- out:
-  g_free (path);
-  return ret;
-}
-
-/* Like g_file_new_for_path, but only do local stuff, not GVFS */
-GFile *
-ht_util_new_file_for_path (const char *path)
-{
-  return g_vfs_get_file_for_path (g_vfs_get_local (), path);
-}
diff --git a/src/libhtutil/ht-gio-utils.h b/src/libhtutil/ht-gio-utils.h
deleted file mode 100644 (file)
index c7342c4..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#ifndef __HACKTREE_GIO_UTILS_H__
-#define __HACKTREE_GIO_UTILS_H__
-
-#include <gio/gio.h>
-
-G_BEGIN_DECLS
-
-GFile *ht_util_new_file_for_path (const char *path);
-
-gboolean ht_util_ensure_directory (const char *path, gboolean with_parents, GError **error);
-
-char * ht_util_get_file_contents_utf8 (const char *path, GError    **error);
-
-GInputStream *ht_util_read_file_noatime (GFile *file, GCancellable *cancellable, GError **error);
-
-G_END_DECLS
-
-#endif
diff --git a/src/libhtutil/ht-unix-utils.c b/src/libhtutil/ht-unix-utils.c
deleted file mode 100644 (file)
index d955725..0000000
+++ /dev/null
@@ -1,257 +0,0 @@
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include "config.h"
-
-#include "ht-unix-utils.h"
-
-#include <glib-unix.h>
-#include <gio/gio.h>
-#include <gio/gunixoutputstream.h>
-
-#include <string.h>
-#include <sys/types.h>
-#include <dirent.h>
-
-gboolean
-ht_util_spawn_pager (GOutputStream  **out_stream,
-                     GError         **error)
-{
-  const char *pager;
-  char *argv[2];
-  int stdin_fd;
-  pid_t pid;
-  gboolean ret = FALSE;
-  GOutputStream *ret_stream = NULL;
-
-  if (!isatty (1))
-    {
-      ret_stream = (GOutputStream*)g_unix_output_stream_new (1, TRUE);
-    }
-  else
-    {
-      pager = g_getenv ("GIT_PAGER");
-      if (pager == NULL)
-        pager = "less";
-      
-      argv[0] = (char*)pager;
-      argv[1] = NULL;
-      
-      if (!g_spawn_async_with_pipes (NULL, argv, NULL, G_SPAWN_SEARCH_PATH,
-                                     NULL, NULL, &pid, &stdin_fd, NULL, NULL, error))
-        {
-          g_prefix_error (error, "%s", "Failed to spawn pager: ");
-          goto out;
-        }
-      
-      ret_stream = (GOutputStream*)g_unix_output_stream_new (stdin_fd, TRUE);
-    }
-
-  *out_stream = ret_stream;
-  ret_stream = NULL;
-  ret = TRUE;
- out:
-  g_clear_object (&ret_stream);
-  return ret;
-}
-
-static int
-compare_filenames_by_component_length (const char *a,
-                                       const char *b)
-{
-  char *a_slash, *b_slash;
-
-  a_slash = strchr (a, '/');
-  b_slash = strchr (b, '/');
-  while (a_slash && b_slash)
-    {
-      a = a_slash + 1;
-      b = b_slash + 1;
-      a_slash = strchr (a, '/');
-      b_slash = strchr (b, '/');
-    }
-  if (a_slash)
-    return -1;
-  else if (b_slash)
-    return 1;
-  else
-    return 0;
-}
-
-GPtrArray *
-ht_util_sort_filenames_by_component_length (GPtrArray *files)
-{
-  GPtrArray *array = g_ptr_array_sized_new (files->len);
-  memcpy (array->pdata, files->pdata, sizeof (gpointer) * files->len);
-  g_ptr_array_sort (array, (GCompareFunc) compare_filenames_by_component_length);
-  return array;
-}
-
-int
-ht_util_count_filename_components (const char *path)
-{
-  int i = 0;
-
-  while (path)
-    {
-      i++;
-      path = strchr (path, '/');
-      if (path)
-        path++;
-    }
-  return i;
-}
-
-gboolean
-ht_util_filename_has_dotdot (const char *path)
-{
-  char *p;
-  char last;
-
-  if (strcmp (path, "..") == 0)
-    return TRUE;
-  if (g_str_has_prefix (path, "../"))
-    return TRUE;
-  p = strstr (path, "/..");
-  if (!p)
-    return FALSE;
-  last = *(p + 1);
-  return last == '\0' || last == '/';
-}
-
-GPtrArray *
-ht_util_path_split (const char *path)
-{
-  GPtrArray *ret = NULL;
-  const char *p;
-  const char *slash;
-  int i;
-
-  g_return_val_if_fail (path[0] != '/', NULL);
-
-  ret = g_ptr_array_new ();
-  g_ptr_array_set_free_func (ret, g_free);
-
-  p = path;
-  do {
-    slash = strchr (p, '/');
-    if (!slash)
-      {
-        g_ptr_array_add (ret, g_strdup (p));
-        p = NULL;
-      }
-    else
-      {
-        g_ptr_array_add (ret, g_strndup (p, slash - p));
-        p = slash + 1;
-      }
-  } while (p && *p);
-
-  /* Canonicalize by removing duplicate '.' */
-  for (i = ret->len-1; i >= 0; i--)
-    {
-      if (strcmp (ret->pdata[i], ".") == 0)
-        g_ptr_array_remove_index (ret, i);
-    }
-
-  return ret;
-}
-
-char *
-ht_util_path_join_n (const char *base, GPtrArray *components, int n)
-{
-  int max = MIN(n+1, components->len);
-  GPtrArray *subcomponents;
-  char *path;
-  int i;
-
-  subcomponents = g_ptr_array_new ();
-
-  if (base != NULL)
-    g_ptr_array_add (subcomponents, (char*)base);
-
-  for (i = 0; i < max; i++)
-    {
-      g_ptr_array_add (subcomponents, components->pdata[i]);
-    }
-  g_ptr_array_add (subcomponents, NULL);
-  
-  path = g_build_filenamev ((char**)subcomponents->pdata);
-  g_ptr_array_free (subcomponents, TRUE);
-  
-  return path;
-}
-
-void
-ht_util_set_error_from_errno (GError **error,
-                              gint     saved_errno)
-{
-  g_set_error_literal (error,
-                       G_UNIX_ERROR,
-                       0,
-                       g_strerror (saved_errno));
-  errno = saved_errno;
-}
-
-int
-ht_util_open_file_read (const char *path, GError **error)
-{
-  char *dirname = NULL;
-  char *basename = NULL;
-  DIR *dir = NULL;
-  int fd = -1;
-
-  dirname = g_path_get_dirname (path);
-  basename = g_path_get_basename (path);
-  dir = opendir (dirname);
-  if (dir == NULL)
-    {
-      ht_util_set_error_from_errno (error, errno);
-      goto out;
-    }
-
-  fd = ht_util_open_file_read_at (dirfd (dir), basename, error);
-
- out:
-  g_free (basename);
-  g_free (dirname);
-  if (dir != NULL)
-    closedir (dir);
-  return fd;
-}
-
-int
-ht_util_open_file_read_at (int dirfd, const char *name, GError **error)
-{
-  int fd;
-  int flags = O_RDONLY;
-  
-#ifdef O_CLOEXEC
-  flags |= O_CLOEXEC;
-#endif
-#ifdef O_NOATIME
-  flags |= O_NOATIME;
-#endif
-  fd = openat (dirfd, name, flags);
-  if (fd < 0)
-    ht_util_set_error_from_errno (error, errno);
-  return fd;
-}
diff --git a/src/libhtutil/ht-unix-utils.h b/src/libhtutil/ht-unix-utils.h
deleted file mode 100644 (file)
index cc027a3..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#ifndef __HACKTREE_UNIX_UTILS_H__
-#define __HACKTREE_UNIX_UTILS_H__
-
-#include <gio/gio.h>
-#include <glib-unix.h>
-
-/* I just put all this shit here. Sue me. */
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <dirent.h>
-#include <string.h>
-#include <fcntl.h>
-#include <stdio.h>
-
-G_BEGIN_DECLS
-
-gboolean ht_util_spawn_pager (GOutputStream  **out_stream, GError         **error);
-
-gboolean ht_util_filename_has_dotdot (const char *path);
-
-GPtrArray *ht_util_sort_filenames_by_component_length (GPtrArray *files);
-
-GPtrArray* ht_util_path_split (const char *path);
-
-char *ht_util_path_join_n (const char *base, GPtrArray *components, int n);
-
-int ht_util_count_filename_components (const char *path);
-
-int ht_util_open_file_read (const char *path, GError **error);
-
-int ht_util_open_file_read_at (int dirfd, const char *name, GError **error);
-
-void ht_util_set_error_from_errno (GError **error, gint saved_errno);
-
-G_END_DECLS
-
-#endif
diff --git a/src/libhtutil/htutil.h b/src/libhtutil/htutil.h
deleted file mode 100644 (file)
index 4434b38..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#ifndef __HACKTREE_UTIL_H__
-
-#include <ht-unix-utils.h>
-#include <ht-gio-utils.h>
-
-#endif
diff --git a/src/libostree/ostree-core.c b/src/libostree/ostree-core.c
new file mode 100644 (file)
index 0000000..ccf35dd
--- /dev/null
@@ -0,0 +1,272 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include "ostree.h"
+#include "otutil.h"
+
+#include <sys/types.h>
+#include <attr/xattr.h>
+
+static char *
+stat_to_string (struct stat *stbuf)
+{
+  return g_strdup_printf ("%u:%u:%u",
+                          (guint32)(stbuf->st_mode & ~S_IFMT),
+                          (guint32)stbuf->st_uid, 
+                          (guint32)stbuf->st_gid);
+}
+
+static char *
+canonicalize_xattrs (char *xattr_string, size_t len)
+{
+  char *p;
+  GSList *xattrs = NULL;
+  GSList *iter;
+  GString *result;
+
+  result = g_string_new (0);
+
+  p = xattr_string;
+  while (p < xattr_string+len)
+    {
+      xattrs = g_slist_prepend (xattrs, p);
+      p += strlen (p) + 1;
+    }
+
+  xattrs = g_slist_sort (xattrs, (GCompareFunc) strcmp);
+  for (iter = xattrs; iter; iter = iter->next)
+    g_string_append (result, iter->data);
+
+  g_slist_free (xattrs);
+  return g_string_free (result, FALSE);
+}
+
+static gboolean
+read_xattr_name_array (const char *path,
+                       const char *xattrs,
+                       size_t      len,
+                       GVariantBuilder *builder,
+                       GError  **error)
+{
+  gboolean ret = FALSE;
+  const char *p;
+
+  p = xattrs;
+  while (p < xattrs+len)
+    {
+      ssize_t bytes_read;
+      char *buf;
+
+      bytes_read = lgetxattr (path, p, NULL, 0);
+      if (bytes_read < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+      if (bytes_read == 0)
+        continue;
+
+      buf = g_malloc (bytes_read);
+      if (lgetxattr (path, p, buf, bytes_read) < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          g_free (buf);
+          goto out;
+        }
+      
+      g_variant_builder_add (builder, "(@ay@ay)",
+                             g_variant_new_bytestring (p),
+                             g_variant_new_fixed_array (G_VARIANT_TYPE ("y"), buf, bytes_read, 1));
+
+      g_free (buf);
+      p = p + strlen (p) + 1;
+    }
+  
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+GVariant *
+ostree_get_xattrs_for_path (const char *path,
+                              GError    **error)
+{
+  GVariant *ret = NULL;
+  GVariantBuilder builder;
+  char *xattr_names = NULL;
+  char *xattr_names_canonical = NULL;
+  ssize_t bytes_read;
+
+  g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayay)"));
+
+  bytes_read = llistxattr (path, NULL, 0);
+
+  if (bytes_read < 0)
+    {
+      if (errno != ENOTSUP)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+    }
+  else if (bytes_read > 0)
+    {
+      const char *p;
+      xattr_names = g_malloc (bytes_read);
+      if (llistxattr (path, xattr_names, bytes_read) < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+      xattr_names_canonical = canonicalize_xattrs (xattr_names, bytes_read);
+      
+      if (!read_xattr_name_array (path, xattr_names_canonical, bytes_read, &builder, error))
+        goto out;
+    }
+
+  ret = g_variant_builder_end (&builder);
+ out:
+  if (!ret)
+    g_variant_builder_clear (&builder);
+  g_free (xattr_names);
+  g_free (xattr_names_canonical);
+  return ret;
+}
+
+gboolean
+ostree_stat_and_checksum_file (int dir_fd, const char *path,
+                                 GChecksum **out_checksum,
+                                 struct stat *out_stbuf,
+                                 GError **error)
+{
+  GChecksum *content_sha256 = NULL;
+  GChecksum *content_and_meta_sha256 = NULL;
+  char *stat_string = NULL;
+  ssize_t bytes_read;
+  GVariant *xattrs = NULL;
+  int fd = -1;
+  DIR *temp_dir = NULL;
+  char *basename = NULL;
+  gboolean ret = FALSE;
+  char *symlink_target = NULL;
+  char *device_id = NULL;
+  struct stat stbuf;
+
+  basename = g_path_get_basename (path);
+
+  if (dir_fd == -1)
+    {
+      char *dirname = g_path_get_dirname (path);
+      temp_dir = opendir (dirname);
+      if (temp_dir == NULL)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          g_free (dirname);
+        }
+      g_free (dirname);
+      dir_fd = dirfd (temp_dir);
+    }
+
+  if (fstatat (dir_fd, basename, &stbuf, AT_SYMLINK_NOFOLLOW) < 0)
+    {
+      ot_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+
+  if (!S_ISLNK(stbuf.st_mode))
+    {
+      fd = ot_util_open_file_read_at (dir_fd, basename, error);
+      if (fd < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+    }
+
+  stat_string = stat_to_string (&stbuf);
+  xattrs = ostree_get_xattrs_for_path (path, error);
+  if (!xattrs)
+    goto out;
+
+  content_sha256 = g_checksum_new (G_CHECKSUM_SHA256);
+  if (S_ISREG(stbuf.st_mode))
+    {
+      guint8 buf[8192];
+
+      while ((bytes_read = read (fd, buf, sizeof (buf))) > 0)
+        g_checksum_update (content_sha256, buf, bytes_read);
+      if (bytes_read < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+    }
+  else if (S_ISLNK(stbuf.st_mode))
+    {
+      symlink_target = g_malloc (PATH_MAX);
+
+      if (readlinkat (dir_fd, basename, symlink_target, PATH_MAX) < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+      g_checksum_update (content_sha256, (guint8*)symlink_target, strlen (symlink_target));
+    }
+  else if (S_ISCHR(stbuf.st_mode) || S_ISBLK(stbuf.st_mode))
+    {
+      device_id = g_strdup_printf ("%u", (guint)stbuf.st_rdev);
+      g_checksum_update (content_sha256, (guint8*)device_id, strlen (device_id));
+    }
+  else
+    {
+      g_set_error (error, G_IO_ERROR,
+                   G_IO_ERROR_FAILED,
+                   "Unsupported file '%s' (must be regular, symbolic link, or device)",
+                   path);
+      goto out;
+    }
+
+  content_and_meta_sha256 = g_checksum_copy (content_sha256);
+
+  g_checksum_update (content_and_meta_sha256, (guint8*)stat_string, strlen (stat_string));
+  g_checksum_update (content_and_meta_sha256, (guint8*)g_variant_get_data (xattrs), g_variant_get_size (xattrs));
+
+  *out_stbuf = stbuf;
+  *out_checksum = content_and_meta_sha256;
+  ret = TRUE;
+ out:
+  if (fd >= 0)
+    close (fd);
+  if (temp_dir != NULL)
+    closedir (temp_dir);
+  g_free (symlink_target);
+  g_free (basename);
+  g_free (stat_string);
+  if (xattrs)
+    g_variant_unref (xattrs);
+  if (content_sha256)
+    g_checksum_free (content_sha256);
+
+  return ret;
+}
diff --git a/src/libostree/ostree-core.h b/src/libostree/ostree-core.h
new file mode 100644 (file)
index 0000000..6d80941
--- /dev/null
@@ -0,0 +1,95 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#ifndef _OSTREE_CORE
+#define _OSTREE_CORE
+
+#include <otutil.h>
+
+G_BEGIN_DECLS
+
+#define OSTREE_EMPTY_STRING_SHA256 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
+
+typedef enum {
+  OSTREE_OBJECT_TYPE_FILE = 1,
+  OSTREE_OBJECT_TYPE_META = 2,
+} OstreeObjectType;
+
+typedef enum {
+  OSTREE_SERIALIZED_TREE_VARIANT = 1,
+  OSTREE_SERIALIZED_COMMIT_VARIANT = 2,
+  OSTREE_SERIALIZED_DIRMETA_VARIANT = 3,
+  OSTREE_SERIALIZED_XATTR_VARIANT = 4
+} OstreeSerializedVariantType;
+
+#define OSTREE_SERIALIZED_VARIANT_FORMAT "(uv)"
+
+/*
+ * xattr objects:
+ * a(ayay) - array of (name, value) pairs, both binary data, though name is a bytestring
+ */
+#define OSTREE_XATTR_GVARIANT_FORMAT "a(ayay)"
+
+#define OSTREE_DIR_META_VERSION 0
+/*
+ * dirmeta objects:
+ * u - Version
+ * u - uid
+ * u - gid
+ * u - mode
+ * a(ayay) - xattrs
+ */
+#define OSTREE_DIRMETA_GVARIANT_FORMAT "(uuuua(ayay))"
+
+#define OSTREE_TREE_VERSION 0
+/*
+ * Tree objects:
+ * u - Version
+ * a{sv} - Metadata
+ * a(ss) - array of (filename, checksum) for files
+ * a(sss) - array of (dirname, tree_checksum, meta_checksum) for directories
+ */
+#define OSTREE_TREE_GVARIANT_FORMAT "(ua{sv}a(ss)a(sss)"
+
+#define OSTREE_COMMIT_VERSION 0
+/*
+ * Commit objects:
+ * u - Version
+ * a{sv} - Metadata
+ * s - parent checksum (empty string for initial)
+ * s - subject 
+ * s - body
+ * t - Timestamp in seconds since the epoch (UTC)
+ * s - Root tree contents
+ * s - Root tree metadata
+ */
+#define OSTREE_COMMIT_GVARIANT_FORMAT "(ua{sv}ssstss)"
+
+GVariant *ostree_get_xattrs_for_path (const char *path,
+                                        GError    **error);
+
+gboolean ostree_stat_and_checksum_file (int dirfd, const char *path,
+                                          GChecksum **out_checksum,
+                                          struct stat *out_stbuf,
+                                          GError **error);
+
+
+#endif /* _OSTREE_REPO */
diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c
new file mode 100644 (file)
index 0000000..4c5b10b
--- /dev/null
@@ -0,0 +1,1769 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include "ostree.h"
+#include "otutil.h"
+
+#include <gio/gunixoutputstream.h>
+#include <gio/gunixinputstream.h>
+
+static gboolean
+link_one_file (OstreeRepo *self, const char *path,
+               OstreeObjectType type,
+               gboolean ignore_exists, gboolean force,
+               GChecksum **out_checksum,
+               GError **error);
+static char *
+get_object_path (OstreeRepo  *self,
+                 const char    *checksum,
+                 OstreeObjectType type);
+
+enum {
+  PROP_0,
+
+  PROP_PATH
+};
+
+G_DEFINE_TYPE (OstreeRepo, ostree_repo, G_TYPE_OBJECT)
+
+#define GET_PRIVATE(o) \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((o), OSTREE_TYPE_REPO, OstreeRepoPrivate))
+
+typedef struct _OstreeRepoPrivate OstreeRepoPrivate;
+
+struct _OstreeRepoPrivate {
+  char *path;
+  GFile *repo_file;
+  char *head_ref_path;
+  char *objects_path;
+
+  gboolean inited;
+  char *current_head;
+};
+
+static void
+ostree_repo_finalize (GObject *object)
+{
+  OstreeRepo *self = OSTREE_REPO (object);
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+
+  g_free (priv->path);
+  g_clear_object (&priv->repo_file);
+  g_free (priv->head_ref_path);
+  g_free (priv->objects_path);
+  g_free (priv->current_head);
+
+  G_OBJECT_CLASS (ostree_repo_parent_class)->finalize (object);
+}
+
+static void
+ostree_repo_set_property(GObject         *object,
+                          guint            prop_id,
+                          const GValue    *value,
+                          GParamSpec      *pspec)
+{
+  OstreeRepo *self = OSTREE_REPO (object);
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+
+  switch (prop_id)
+    {
+    case PROP_PATH:
+      priv->path = g_value_dup_string (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+ostree_repo_get_property(GObject         *object,
+                          guint            prop_id,
+                          GValue          *value,
+                          GParamSpec      *pspec)
+{
+  OstreeRepo *self = OSTREE_REPO (object);
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+
+  switch (prop_id)
+    {
+    case PROP_PATH:
+      g_value_set_string (value, priv->path);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static GObject *
+ostree_repo_constructor (GType                  gtype,
+                           guint                  n_properties,
+                           GObjectConstructParam *properties)
+{
+  GObject *object;
+  GObjectClass *parent_class;
+  OstreeRepoPrivate *priv;
+
+  parent_class = G_OBJECT_CLASS (ostree_repo_parent_class);
+  object = parent_class->constructor (gtype, n_properties, properties);
+
+  priv = GET_PRIVATE (object);
+
+  g_assert (priv->path != NULL);
+  
+  priv->repo_file = ot_util_new_file_for_path (priv->path);
+  priv->head_ref_path = g_build_filename (priv->path, OSTREE_REPO_DIR, "HEAD", NULL);
+  priv->objects_path = g_build_filename (priv->path, OSTREE_REPO_DIR, "objects", NULL);
+
+  return object;
+}
+
+static void
+ostree_repo_class_init (OstreeRepoClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (OstreeRepoPrivate));
+
+  object_class->constructor = ostree_repo_constructor;
+  object_class->get_property = ostree_repo_get_property;
+  object_class->set_property = ostree_repo_set_property;
+  object_class->finalize = ostree_repo_finalize;
+
+  g_object_class_install_property (object_class,
+                                   PROP_PATH,
+                                   g_param_spec_string ("path",
+                                                        "",
+                                                        "",
+                                                        NULL,
+                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+ostree_repo_init (OstreeRepo *self)
+{
+}
+
+OstreeRepo*
+ostree_repo_new (const char *path)
+{
+  return g_object_new (OSTREE_TYPE_REPO, "path", path, NULL);
+}
+
+static gboolean
+parse_checksum_file (OstreeRepo   *self,
+                     const char     *path,
+                     char          **sha256,
+                     GError        **error)
+{
+  GError *temp_error = NULL;
+  gboolean ret = FALSE;
+  char *ret_sha256 = NULL;
+
+  ret_sha256 = ot_util_get_file_contents_utf8 (path, &temp_error);
+  if (ret_sha256 == NULL)
+    {
+      if (g_error_matches (temp_error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
+        {
+          g_clear_error (&temp_error);
+        }
+      else
+        {
+          g_propagate_error (error, temp_error);
+          goto out;
+        }
+    }
+  else
+    {
+      g_strchomp (ret_sha256);
+    }
+
+  *sha256 = ret_sha256;
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+static gboolean
+write_checksum_file (const char *path,
+                     const char *sha256,
+                     GError    **error)
+{
+  gboolean ret = FALSE;
+  char *buf = NULL;
+
+  buf = g_strconcat (sha256, "\n", NULL);
+  
+  if (!g_file_set_contents (path, buf, -1, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  g_free (buf);
+  return ret;
+}
+
+gboolean
+ostree_repo_check (OstreeRepo *self, GError **error)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  if (priv->inited)
+    return TRUE;
+
+  if (!g_file_test (priv->objects_path, G_FILE_TEST_IS_DIR))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Couldn't find objects directory '%s'", priv->objects_path);
+      return FALSE;
+    }
+  
+  priv->inited = TRUE;
+
+  return parse_checksum_file (self, priv->head_ref_path, &priv->current_head, error);
+}
+
+static gboolean
+import_gvariant_object (OstreeRepo  *self,
+                        OstreeSerializedVariantType type,
+                        GVariant       *variant,
+                        GChecksum    **out_checksum,
+                        GError       **error)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  GVariant *serialized = NULL;
+  gboolean ret = FALSE;
+  gsize bytes_written;
+  char *tmp_name = NULL;
+  int fd = -1;
+  GUnixOutputStream *stream = NULL;
+
+  serialized = g_variant_new ("(uv)", (guint32)type, variant);
+
+  tmp_name = g_build_filename (priv->objects_path, "variant-tmp-XXXXXX", NULL);
+  fd = mkstemp (tmp_name);
+  if (fd < 0)
+    {
+      ot_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+
+  stream = (GUnixOutputStream*)g_unix_output_stream_new (fd, FALSE);
+  if (!g_output_stream_write_all ((GOutputStream*)stream,
+                                  g_variant_get_data (serialized),
+                                  g_variant_get_size (serialized),
+                                  &bytes_written,
+                                  NULL,
+                                  error))
+    goto out;
+  if (!g_output_stream_close ((GOutputStream*)stream,
+                              NULL, error))
+    goto out;
+
+  if (!link_one_file (self, tmp_name, OSTREE_OBJECT_TYPE_META, 
+                      TRUE, FALSE, out_checksum, error))
+    goto out;
+  
+  ret = TRUE;
+ out:
+  /* Unconditionally unlink; if we suceeded, there's a new link, if not, clean up. */
+  (void) unlink (tmp_name);
+  if (fd != -1)
+    close (fd);
+  if (serialized != NULL)
+    g_variant_unref (serialized);
+  g_free (tmp_name);
+  g_clear_object (&stream);
+  return ret;
+}
+
+static gboolean
+load_gvariant_object_unknown (OstreeRepo  *self,
+                              const char    *sha256,
+                              OstreeSerializedVariantType *out_type,
+                              GVariant     **out_variant,
+                              GError       **error)
+{
+  GMappedFile *mfile = NULL;
+  gboolean ret = FALSE;
+  GVariant *ret_variant = NULL;
+  GVariant *container = NULL;
+  char *path = NULL;
+  guint32 ret_type;
+
+  path = get_object_path (self, sha256, OSTREE_OBJECT_TYPE_META);
+  
+  mfile = g_mapped_file_new (path, FALSE, error);
+  if (mfile == NULL)
+    goto out;
+  else
+    {
+      container = g_variant_new_from_data (G_VARIANT_TYPE (OSTREE_SERIALIZED_VARIANT_FORMAT),
+                                           g_mapped_file_get_contents (mfile),
+                                           g_mapped_file_get_length (mfile),
+                                           FALSE,
+                                           (GDestroyNotify) g_mapped_file_unref,
+                                           mfile);
+      if (!g_variant_is_of_type (container, G_VARIANT_TYPE (OSTREE_SERIALIZED_VARIANT_FORMAT)))
+        {
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       "Corrupted metadata object '%s'", sha256);
+          goto out;
+        }
+      g_variant_get (container, "(uv)",
+                     &ret_type, &ret_variant);
+      mfile = NULL;
+    }
+
+  ret = TRUE;
+ out:
+  if (!ret)
+    {
+      if (ret_variant)
+        g_variant_unref (ret_variant);
+    }
+  else
+    {
+      *out_type = ret_type;
+      *out_variant = ret_variant;
+    }
+  if (container != NULL)
+    g_variant_unref (container);
+  g_free (path);
+  if (mfile != NULL)
+    g_mapped_file_unref (mfile);
+  return ret;
+}
+
+static gboolean
+load_gvariant_object (OstreeRepo  *self,
+                      OstreeSerializedVariantType expected_type,
+                      const char    *sha256, 
+                      GVariant     **out_variant,
+                      GError       **error)
+{
+  gboolean ret = FALSE;
+  OstreeSerializedVariantType type;
+  GVariant *ret_variant = NULL;
+
+  if (!load_gvariant_object_unknown (self, sha256, &type, &ret_variant, error))
+    goto out;
+
+  if (type != expected_type)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Corrupted metadata object '%s'; found type %u, expected %u", sha256,
+                   type, (guint32)expected_type);
+      goto out;
+      
+    }
+
+  ret = TRUE;
+  *out_variant = ret_variant;
+ out:
+  if (!ret)
+    {
+      if (ret_variant)
+        g_variant_unref (ret_variant);
+    }
+  return ret;
+}
+
+static gboolean
+import_directory_meta (OstreeRepo  *self,
+                       const char *path,
+                       GVariant  **out_variant,
+                       GChecksum **out_checksum,
+                       GError    **error)
+{
+  gboolean ret = FALSE;
+  struct stat stbuf;
+  GChecksum *ret_checksum = NULL;
+  GVariant *dirmeta = NULL;
+  GVariant *xattrs = NULL;
+  gsize xattr_len;
+
+  if (lstat (path, &stbuf) < 0)
+    {
+      ot_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+  
+  if (!S_ISDIR(stbuf.st_mode))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Not a directory: '%s'", path);
+      goto out;
+    }
+
+  xattrs = ostree_get_xattrs_for_path (path, error);
+  if (!xattrs)
+    goto out;
+
+  dirmeta = g_variant_new ("(uuuu@a(ayay))",
+                           OSTREE_DIR_META_VERSION,
+                           (guint32)stbuf.st_uid,
+                           (guint32)stbuf.st_gid,
+                           (guint32)(stbuf.st_mode & (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)),
+                           xattrs);
+  xattrs = NULL; /* was floating */
+  g_variant_ref_sink (dirmeta);
+
+  if (!import_gvariant_object (self, OSTREE_SERIALIZED_DIRMETA_VARIANT, 
+                               dirmeta, &ret_checksum, error))
+        goto out;
+
+  ret = TRUE;
+ out:
+  if (!ret)
+    {
+      if (ret_checksum)
+        g_checksum_free (ret_checksum);
+      if (dirmeta != NULL)
+        g_variant_unref (dirmeta);
+    }
+  else
+    {
+      *out_checksum = ret_checksum;
+      *out_variant = dirmeta;
+    }
+  if (xattrs)
+    g_variant_unref (xattrs);
+  return ret;
+}
+
+static char *
+get_object_path (OstreeRepo  *self,
+                 const char    *checksum,
+                 OstreeObjectType type)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  char *checksum_prefix;
+  char *base_path;
+  char *ret;
+  const char *type_string;
+
+  checksum_prefix = g_strndup (checksum, 2);
+  base_path = g_build_filename (priv->objects_path, checksum_prefix, checksum + 2, NULL);
+  switch (type)
+    {
+    case OSTREE_OBJECT_TYPE_FILE:
+      type_string = ".file";
+      break;
+    case OSTREE_OBJECT_TYPE_META:
+      type_string = ".meta";
+      break;
+    default:
+      g_assert_not_reached ();
+    }
+  ret = g_strconcat (base_path, type_string, NULL);
+  g_free (base_path);
+  g_free (checksum_prefix);
+  return ret;
+}
+
+static char *
+prepare_dir_for_checksum_get_object_path (OstreeRepo *self,
+                                          GChecksum    *checksum,
+                                          OstreeObjectType type,
+                                          GError      **error)
+{
+  char *checksum_dir = NULL;
+  char *object_path = NULL;
+
+  object_path = get_object_path (self, g_checksum_get_string (checksum), type);
+  checksum_dir = g_path_get_dirname (object_path);
+
+  if (!ot_util_ensure_directory (checksum_dir, FALSE, error))
+    goto out;
+  
+ out:
+  g_free (checksum_dir);
+  return object_path;
+}
+
+static gboolean
+link_one_file (OstreeRepo *self, const char *path, OstreeObjectType type,
+               gboolean ignore_exists, gboolean force,
+               GChecksum **out_checksum,
+               GError **error)
+{
+  char *src_basename = NULL;
+  char *src_dirname = NULL;
+  char *dest_basename = NULL;
+  char *tmp_dest_basename = NULL;
+  char *dest_dirname = NULL;
+  GChecksum *id = NULL;
+  DIR *src_dir = NULL;
+  DIR *dest_dir = NULL;
+  gboolean ret = FALSE;
+  struct stat stbuf;
+  char *dest_path = NULL;
+
+  src_basename = g_path_get_basename (path);
+  src_dirname = g_path_get_dirname (path);
+
+  src_dir = opendir (src_dirname);
+  if (src_dir == NULL)
+    {
+      ot_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+
+  if (!ostree_stat_and_checksum_file (dirfd (src_dir), path, &id, &stbuf, error))
+    goto out;
+  dest_path = prepare_dir_for_checksum_get_object_path (self, id, type, error);
+  if (!dest_path)
+    goto out;
+
+  dest_basename = g_path_get_basename (dest_path);
+  dest_dirname = g_path_get_dirname (dest_path);
+  dest_dir = opendir (dest_dirname);
+  if (dest_dir == NULL)
+    {
+      ot_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+
+  if (force)
+    {
+      tmp_dest_basename = g_strconcat (dest_basename, ".tmp", NULL);
+      (void) unlinkat (dirfd (dest_dir), tmp_dest_basename, 0);
+    }
+  else
+    tmp_dest_basename = g_strdup (dest_basename);
+  
+  if (linkat (dirfd (src_dir), src_basename, dirfd (dest_dir), tmp_dest_basename, 0) < 0)
+    {
+      if (errno != EEXIST || !ignore_exists)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+    }
+
+  if (force)
+    {
+      if (renameat (dirfd (dest_dir), tmp_dest_basename, 
+                    dirfd (dest_dir), dest_basename) < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+      (void) unlinkat (dirfd (dest_dir), tmp_dest_basename, 0);
+    }
+
+  *out_checksum = id;
+  id = NULL;
+  ret = TRUE;
+ out:
+  if (id != NULL)
+    g_checksum_free (id);
+  if (src_dir != NULL)
+    closedir (src_dir);
+  if (dest_dir != NULL)
+    closedir (dest_dir);
+  g_free (src_basename);
+  g_free (src_dirname);
+  g_free (dest_basename);
+  g_free (tmp_dest_basename);
+  g_free (dest_dirname);
+  return ret;
+}
+
+gboolean
+ostree_repo_link_file (OstreeRepo *self,
+                         const char   *path,
+                         gboolean      ignore_exists,
+                         gboolean      force,
+                         GError      **error)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  GChecksum *checksum = NULL;
+
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+  g_return_val_if_fail (priv->inited, FALSE);
+
+  if (!link_one_file (self, path, OSTREE_OBJECT_TYPE_FILE,
+                      ignore_exists, force, &checksum, error))
+    return FALSE;
+  g_checksum_free (checksum);
+  return TRUE;
+}
+
+typedef struct _ParsedTreeData ParsedTreeData;
+typedef struct _ParsedDirectoryData ParsedDirectoryData;
+
+static void parsed_tree_data_free (ParsedTreeData *pdata);
+
+struct _ParsedDirectoryData {
+  ParsedTreeData *tree_data;
+  char *metadata_sha256;
+  GVariant *meta_data;
+};
+
+static void
+parsed_directory_data_free (ParsedDirectoryData *pdata)
+{
+  if (pdata == NULL)
+    return;
+  parsed_tree_data_free (pdata->tree_data);
+  g_free (pdata->metadata_sha256);
+  g_variant_unref (pdata->meta_data);
+  g_free (pdata);
+}
+
+struct _ParsedTreeData {
+  GHashTable *files;  /* char* filename -> char* checksum */
+  GHashTable *directories;  /* char* dirname -> ParsedDirectoryData* */
+};
+
+static ParsedTreeData *
+parsed_tree_data_new (void)
+{
+  ParsedTreeData *ret = g_new0 (ParsedTreeData, 1);
+  ret->files = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                      (GDestroyNotify)g_free, 
+                                      (GDestroyNotify)g_free);
+  ret->directories = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                            (GDestroyNotify)g_free, 
+                                            (GDestroyNotify)parsed_directory_data_free);
+  return ret;
+}
+
+static void
+parsed_tree_data_free (ParsedTreeData *pdata)
+{
+  if (pdata == NULL)
+    return;
+  g_hash_table_destroy (pdata->files);
+  g_hash_table_destroy (pdata->directories);
+  g_free (pdata);
+}
+
+static gboolean
+parse_tree (OstreeRepo    *self,
+            const char      *sha256,
+            ParsedTreeData **out_pdata,
+            GError         **error)
+{
+  gboolean ret = FALSE;
+  ParsedTreeData *ret_pdata = NULL;
+  int i, n;
+  guint32 version;
+  GVariant *tree_variant = NULL;
+  GVariant *meta_variant = NULL;
+  GVariant *files_variant = NULL;
+  GVariant *dirs_variant = NULL;
+
+  if (!load_gvariant_object (self, OSTREE_SERIALIZED_TREE_VARIANT,
+                             sha256, &tree_variant, error))
+    goto out;
+
+  g_variant_get (tree_variant, "(u@a{sv}@a(ss)@a(sss))",
+                 &version, &meta_variant, &files_variant, &dirs_variant);
+
+  ret_pdata = parsed_tree_data_new ();
+  n = g_variant_n_children (files_variant);
+  for (i = 0; i < n; i++)
+    {
+      const char *filename;
+      const char *checksum;
+
+      g_variant_get_child (files_variant, i, "(ss)", &filename, &checksum);
+
+      g_hash_table_insert (ret_pdata->files, g_strdup (filename), g_strdup (checksum));
+    }
+
+  n = g_variant_n_children (dirs_variant);
+  for (i = 0; i < n; i++)
+    {
+      const char *dirname;
+      const char *tree_checksum;
+      const char *meta_checksum;
+      ParsedTreeData *child_tree = NULL;
+      GVariant *metadata = NULL;
+      ParsedDirectoryData *child_dir = NULL;
+
+      g_variant_get_child (dirs_variant, i, "(sss)",
+                           &dirname, &tree_checksum, &meta_checksum);
+      
+      if (!parse_tree (self, tree_checksum, &child_tree, error))
+        goto out;
+
+      if (!load_gvariant_object (self, OSTREE_SERIALIZED_DIRMETA_VARIANT,
+                                 meta_checksum, &metadata, error))
+        {
+          parsed_tree_data_free (child_tree);
+          goto out;
+        }
+
+      child_dir = g_new0 (ParsedDirectoryData, 1);
+      child_dir->tree_data = child_tree;
+      child_dir->metadata_sha256 = g_strdup (meta_checksum);
+      child_dir->meta_data = g_variant_ref_sink (metadata);
+
+      g_hash_table_insert (ret_pdata->directories, g_strdup (dirname), child_dir);
+    }
+
+  ret = TRUE;
+ out:
+  if (!ret)
+    parsed_tree_data_free (ret_pdata);
+  else
+    *out_pdata = ret_pdata;
+  if (tree_variant)
+    g_variant_unref (tree_variant);
+  if (meta_variant)
+    g_variant_unref (meta_variant);
+  if (files_variant)
+    g_variant_unref (files_variant);
+  if (dirs_variant)
+    g_variant_unref (dirs_variant);
+  return ret;
+}
+
+static gboolean
+load_commit_and_trees (OstreeRepo   *self,
+                       const char     *commit_sha256,
+                       GVariant      **out_commit,
+                       ParsedDirectoryData **out_root_data,
+                       GError        **error)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  GVariant *ret_commit = NULL;
+  ParsedDirectoryData *ret_root_data = NULL;
+  ParsedTreeData *tree_data = NULL;
+  char *ret_metadata_checksum = NULL;
+  GVariant *root_metadata = NULL;
+  gboolean ret = FALSE;
+  const char *tree_contents_checksum;
+  const char *tree_meta_checksum;
+
+  if (!priv->current_head)
+    {
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "Can't load current commit; no HEAD reference");
+      goto out;
+    }
+
+  if (!load_gvariant_object (self, OSTREE_SERIALIZED_COMMIT_VARIANT,
+                             commit_sha256, &ret_commit, error))
+    goto out;
+
+  g_variant_get_child (ret_commit, 6, "&s", &tree_contents_checksum);
+  g_variant_get_child (ret_commit, 7, "&s", &tree_meta_checksum);
+
+  if (!load_gvariant_object (self, OSTREE_SERIALIZED_DIRMETA_VARIANT,
+                             tree_meta_checksum, &root_metadata, error))
+    goto out;
+
+  if (!parse_tree (self, tree_contents_checksum, &tree_data, error))
+    goto out;
+
+  ret_root_data = g_new0 (ParsedDirectoryData, 1);
+  ret_root_data->tree_data = tree_data;
+  ret_root_data->metadata_sha256 = g_strdup (tree_meta_checksum);
+  ret_root_data->meta_data = root_metadata;
+  root_metadata = NULL;
+
+  ret = TRUE;
+  *out_commit = ret_commit;
+  ret_commit = NULL;
+  *out_root_data = ret_root_data;
+  ret_root_data = NULL;
+ out:
+  if (ret_commit)
+    g_variant_unref (ret_commit);
+  parsed_directory_data_free (ret_root_data);
+  g_free (ret_metadata_checksum);
+  if (root_metadata)
+    g_variant_unref (root_metadata);
+  return ret;
+}
+
+static GVariant *
+create_empty_gvariant_dict (void)
+{
+  GVariantBuilder builder;
+  g_variant_builder_init (&builder, G_VARIANT_TYPE("a{sv}"));
+  return g_variant_builder_end (&builder);
+}
+
+static gboolean
+import_parsed_tree (OstreeRepo    *self,
+                    ParsedTreeData  *tree,
+                    GChecksum      **out_checksum,
+                    GError         **error)
+{
+  gboolean ret = FALSE;
+  GVariant *serialized_tree = NULL;
+  gboolean builders_initialized = FALSE;
+  GVariantBuilder files_builder;
+  GVariantBuilder dirs_builder;
+  GHashTableIter hash_iter;
+  gpointer key, value;
+
+  g_variant_builder_init (&files_builder, G_VARIANT_TYPE ("a(ss)"));
+  g_variant_builder_init (&dirs_builder, G_VARIANT_TYPE ("a(sss)"));
+  builders_initialized = TRUE;
+
+  g_hash_table_iter_init (&hash_iter, tree->files);
+  while (g_hash_table_iter_next (&hash_iter, &key, &value))
+    {
+      const char *name = key;
+      const char *checksum = value;
+
+      g_variant_builder_add (&files_builder, "(ss)", name, checksum);
+    }
+
+  g_hash_table_iter_init (&hash_iter, tree->directories);
+  while (g_hash_table_iter_next (&hash_iter, &key, &value))
+    {
+      const char *name = key;
+      GChecksum *dir_checksum = NULL;
+      ParsedDirectoryData *dir = value;
+
+      if (!import_parsed_tree (self, dir->tree_data, &dir_checksum, error))
+        goto out;
+
+      g_variant_builder_add (&dirs_builder, "(sss)",
+                             name, g_checksum_get_string (dir_checksum), dir->metadata_sha256);
+    }
+
+  serialized_tree = g_variant_new ("(u@a{sv}@a(ss)@a(sss))",
+                                   0,
+                                   create_empty_gvariant_dict (),
+                                   g_variant_builder_end (&files_builder),
+                                   g_variant_builder_end (&dirs_builder));
+  builders_initialized = FALSE;
+  g_variant_ref_sink (serialized_tree);
+  if (!import_gvariant_object (self, OSTREE_SERIALIZED_TREE_VARIANT, serialized_tree, out_checksum, error))
+    goto out;
+  
+  ret = TRUE;
+ out:
+  if (builders_initialized)
+    {
+      g_variant_builder_clear (&files_builder);
+      g_variant_builder_clear (&dirs_builder);
+    }
+  if (serialized_tree)
+    g_variant_unref (serialized_tree);
+  return ret;
+}
+
+static gboolean
+check_path (const char *filename,
+            GError    **error)
+{
+  gboolean ret = FALSE;
+
+  if (!*filename)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Invalid empty filename");
+      goto out;
+    }
+
+  if (strcmp (filename, ".") == 0)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Self-reference '.' in filename '%s' not allowed (yet)", filename);
+      goto out;
+    }
+  
+  if (ot_util_filename_has_dotdot (filename))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Path uplink '..' in filename '%s' not allowed (yet)", filename);
+      goto out;
+    }
+  
+  if (g_path_is_absolute (filename))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Absolute filename '%s' not allowed (yet)", filename);
+      goto out;
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+static gboolean
+walk_parsed_tree (OstreeRepo  *self,
+                  const char    *filename,
+                  ParsedTreeData *tree,
+                  int            *out_filename_index, /* out*/
+                  char          **out_component, /* out, must free */
+                  ParsedTreeData **out_tree, /* out, but do not free */
+                  GError        **error)
+{
+  gboolean ret = FALSE;
+  GPtrArray *components = NULL;
+  ParsedTreeData *current_tree = tree;
+  const char *component = NULL;
+  const char *file_sha1 = NULL;
+  ParsedDirectoryData *dir = NULL;
+  int i;
+  int ret_filename_index = 0;
+
+  components = ot_util_path_split (filename);
+  g_assert (components != NULL);
+
+  current_tree = tree;
+  for (i = 0; i < components->len - 1; i++)
+    {
+      component = components->pdata[i];
+      file_sha1 = g_hash_table_lookup (current_tree->files, component);
+      dir = g_hash_table_lookup (current_tree->directories, component);
+
+      if (!(file_sha1 || dir))
+        {
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       "No such file or directory: %s",
+                       filename);
+          goto out;
+        }
+      else if (file_sha1)
+        {
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       "Encountered non-directory '%s' in '%s'",
+                       (char*)component,
+                       filename);
+          goto out;
+        }
+      else
+        {
+          g_assert (dir != NULL);
+          current_tree = dir->tree_data;
+          ret_filename_index++;
+        }
+    }
+
+  ret = TRUE;
+  *out_filename_index = i;
+  *out_component = components->pdata[components->len-1];
+  components->pdata[components->len-1] = NULL; /* steal */
+  *out_tree = current_tree;
+ out:
+  g_ptr_array_free (components, TRUE);
+  return ret;
+}
+
+static gboolean
+remove_files_from_tree (OstreeRepo   *self,
+                        const char     *base,
+                        GPtrArray      *removed_files,
+                        ParsedTreeData *tree,
+                        GError        **error)
+{
+  gboolean ret = FALSE;
+  int i;
+
+  for (i = 0; i < removed_files->len; i++)
+    {
+      const char *filename = removed_files->pdata[i];
+      int filename_index;
+      char *component = NULL;
+      ParsedTreeData *parent;
+      const char *file_sha1;
+      ParsedTreeData *dir;
+
+      if (!check_path (filename, error))
+        goto out;
+       
+      if (!walk_parsed_tree (self, filename, tree,
+                             &filename_index, (char**)&component, &parent,
+                             error))
+        goto out;
+
+      file_sha1 = g_hash_table_lookup (parent->files, component);
+      dir = g_hash_table_lookup (parent->directories, component);
+
+      if (file_sha1)
+        g_hash_table_remove (parent->files, component);
+      else if (dir)
+        g_hash_table_remove (parent->directories, component);
+      else
+        {
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       "No such file or directory: %s",
+                       filename);
+          g_free (component);
+          goto out;
+        }
+      g_free (component);
+    }
+  
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+static gboolean
+add_one_directory_to_tree_and_import (OstreeRepo   *self,
+                                      const char     *basename,
+                                      const char     *abspath,
+                                      ParsedTreeData *tree,
+                                      ParsedDirectoryData **dir, /*inout*/
+                                      GError        **error)
+{
+  gboolean ret = FALSE;
+  GVariant *dirmeta = NULL;
+  GChecksum *dir_meta_checksum = NULL;
+  ParsedDirectoryData *dir_value = *dir;
+  
+  g_assert (tree != NULL);
+
+  if (!import_directory_meta (self, abspath, &dirmeta, &dir_meta_checksum, error))
+    goto out;
+
+  if (dir_value)
+    {
+      g_variant_unref (dir_value->meta_data);
+      dir_value->meta_data = dirmeta;
+    }
+  else
+    {
+      dir_value = g_new0 (ParsedDirectoryData, 1);
+      dir_value->tree_data = parsed_tree_data_new ();
+      dir_value->metadata_sha256 = g_strdup (g_checksum_get_string (dir_meta_checksum));
+      dir_value->meta_data = dirmeta;
+      g_hash_table_insert (tree->directories, g_strdup (basename), dir_value);
+    }
+
+  ret = TRUE;
+  *dir = dir_value;
+ out:
+  if (dir_meta_checksum)
+    g_checksum_free (dir_meta_checksum);
+  return ret;
+}
+
+static gboolean
+add_one_file_to_tree_and_import (OstreeRepo   *self,
+                                 const char     *basename,
+                                 const char     *abspath,
+                                 ParsedTreeData *tree,
+                                 GError        **error)
+{
+  gboolean ret = FALSE;
+  GChecksum *checksum = NULL;
+  
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+  g_assert (tree != NULL);
+
+  if (!link_one_file (self, abspath, OSTREE_OBJECT_TYPE_FILE,
+                      TRUE, FALSE, &checksum, error))
+    goto out;
+
+  g_hash_table_replace (tree->files, g_strdup (basename),
+                        g_strdup (g_checksum_get_string (checksum)));
+
+  ret = TRUE;
+ out:
+  if (checksum)
+    g_checksum_free (checksum);
+  return ret;
+}
+
+static gboolean
+add_one_path_to_tree_and_import (OstreeRepo   *self,
+                                 const char     *base,
+                                 const char     *filename,
+                                 ParsedTreeData *tree,
+                                 GError        **error)
+{
+  gboolean ret = FALSE;
+  GPtrArray *components = NULL;
+  struct stat stbuf;
+  char *component_abspath = NULL;
+  ParsedTreeData *current_tree = tree;
+  const char *component = NULL;
+  const char *file_sha1;
+  char *abspath = NULL;
+  ParsedDirectoryData *dir;
+  int i;
+  gboolean is_directory;
+
+  if (!check_path (filename, error))
+    goto out;
+
+  abspath = g_build_filename (base, filename, NULL);
+
+  if (lstat (abspath, &stbuf) < 0)
+    {
+      ot_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+  is_directory = S_ISDIR(stbuf.st_mode);
+       
+  if (components)
+    g_ptr_array_free (components, TRUE);
+  components = ot_util_path_split (filename);
+  g_assert (components->len > 0);
+
+  current_tree = tree;
+  for (i = 0; i < components->len; i++)
+    {
+      component = components->pdata[i];
+      g_free (component_abspath);
+      component_abspath = ot_util_path_join_n (base, components, i);
+      file_sha1 = g_hash_table_lookup (current_tree->files, component);
+      dir = g_hash_table_lookup (current_tree->directories, component);
+
+      g_assert_cmpstr (component, !=, ".");
+
+      if (i < components->len - 1)
+        {
+          if (file_sha1 != NULL)
+            {
+              g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "Encountered non-directory '%s' in '%s'",
+                           component,
+                           filename);
+              goto out;
+            }
+          /* Implicitly add intermediate directories */
+          if (!add_one_directory_to_tree_and_import (self, component,
+                                                     component_abspath, current_tree, &dir,
+                                                     error))
+            goto out;
+          g_assert (dir != NULL);
+          current_tree = dir->tree_data;
+        }
+      else if (is_directory)
+        {
+          if (file_sha1 != NULL)
+            {
+              g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "File '%s' can't be overwritten by directory",
+                           filename);
+              goto out;
+            }
+          if (!add_one_directory_to_tree_and_import (self, component,
+                                                     abspath, current_tree, &dir,
+                                                     error))
+            goto out;
+        }
+      else 
+        {
+          g_assert (!is_directory);
+          if (dir != NULL)
+            {
+              g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "File '%s' can't be overwritten by directory",
+                           filename);
+              goto out;
+            }
+          if (!add_one_file_to_tree_and_import (self, component, abspath,
+                                                current_tree, error))
+            goto out;
+        }
+    }
+
+  ret = TRUE;
+ out:
+  g_free (component_abspath);
+  g_free (abspath);
+  return ret;
+}
+
+static gboolean
+add_files_to_tree_and_import (OstreeRepo   *self,
+                              const char     *base,
+                              GPtrArray      *added_files,
+                              ParsedTreeData *tree,
+                              GError        **error)
+{
+  gboolean ret = FALSE;
+  int i;
+
+  for (i = 0; i < added_files->len; i++)
+    {
+      const char *path = added_files->pdata[i];
+
+      if (!add_one_path_to_tree_and_import (self, base, path, tree, error))
+        goto out;
+    }
+  
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+static gboolean
+commit_parsed_tree (OstreeRepo *self,
+                    const char   *subject,
+                    const char   *body,
+                    GVariant     *metadata,
+                    ParsedDirectoryData *root,
+                    GChecksum   **out_commit,
+                    GError      **error)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  gboolean ret = FALSE;
+  GChecksum *root_checksum = NULL;
+  GChecksum *ret_commit = NULL;
+  GVariant *commit = NULL;
+  GDateTime *now = NULL;
+
+  if (!import_parsed_tree (self, root->tree_data, &root_checksum, error))
+    goto out;
+
+  now = g_date_time_new_now_utc ();
+  commit = g_variant_new ("(u@a{sv}ssstss)",
+                          OSTREE_COMMIT_VERSION,
+                          create_empty_gvariant_dict (),
+                          priv->current_head ? priv->current_head : "",
+                          subject, body ? body : "",
+                          g_date_time_to_unix (now) / G_TIME_SPAN_SECOND,
+                          g_checksum_get_string (root_checksum),
+                          root->metadata_sha256);
+  if (!import_gvariant_object (self, OSTREE_SERIALIZED_COMMIT_VARIANT,
+                               commit, &ret_commit, error))
+    goto out;
+
+  if (!write_checksum_file (priv->head_ref_path, g_checksum_get_string (ret_commit), error))
+    goto out;
+
+  g_free (priv->current_head);
+  priv->current_head = g_strdup (g_checksum_get_string (ret_commit));
+
+  ret = TRUE;
+  *out_commit = ret_commit;
+ out:
+  if (root_checksum)
+    g_checksum_free (root_checksum);
+  if (commit)
+    g_variant_unref (commit);
+  if (now)
+    g_date_time_unref (now);
+  return ret;
+}
+
+static gboolean
+import_root (OstreeRepo     *self,
+             const char        *base,
+             ParsedDirectoryData **out_root,
+             GError              **error)
+{
+  gboolean ret = FALSE;
+  ParsedDirectoryData *ret_root = NULL;
+  GVariant *root_metadata = NULL;
+  GChecksum *root_meta_checksum = NULL;
+
+  if (!import_directory_meta (self, base, &root_metadata, &root_meta_checksum, error))
+    goto out;
+
+  ret_root = g_new0 (ParsedDirectoryData, 1);
+  ret_root->tree_data = parsed_tree_data_new ();
+  ret_root->meta_data = root_metadata;
+  root_metadata = NULL;
+  ret_root->metadata_sha256 = g_strdup (g_checksum_get_string (root_meta_checksum));
+
+  ret = TRUE;
+  *out_root = ret_root;
+  ret_root = NULL;
+ out:
+  if (root_metadata)
+    g_variant_unref (root_metadata);
+  if (root_meta_checksum)
+    g_checksum_free (root_meta_checksum);
+  parsed_directory_data_free (ret_root);
+  return ret;
+}
+
+gboolean
+ostree_repo_commit (OstreeRepo *self,
+                      const char   *subject,
+                      const char   *body,
+                      GVariant     *metadata,
+                      const char   *base,
+                      GPtrArray    *modified_files,
+                      GPtrArray    *removed_files,
+                      GChecksum   **out_commit,
+                      GError      **error)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  gboolean ret = FALSE;
+  ParsedDirectoryData *root = NULL;
+  GVariant *previous_commit = NULL;
+  GChecksum *ret_commit_checksum = NULL;
+  char *orig_root_metadata_sha256 = NULL;
+  GVariant *root_metadata = NULL;
+  GChecksum *root_meta_checksum = NULL;
+
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+  g_return_val_if_fail (priv->inited, FALSE);
+
+  if (priv->current_head)
+    {
+      if (!load_commit_and_trees (self, priv->current_head, &previous_commit, &root, error))
+        goto out;
+      if (!import_directory_meta (self, base, &root_metadata, &root_meta_checksum, error))
+        goto out;
+      g_variant_unref (root->meta_data);
+      root->meta_data = root_metadata;
+      root_metadata = NULL;
+      root->metadata_sha256 = g_strdup (g_checksum_get_string (root_meta_checksum));
+    }
+  else
+    {
+      /* Initial commit */
+      if (!import_root (self, base, &root, error))
+        goto out;
+    }
+
+  if (!remove_files_from_tree (self, base, removed_files, root->tree_data, error))
+    goto out;
+
+  if (!add_files_to_tree_and_import (self, base, modified_files, root->tree_data, error))
+    goto out;
+
+  if (!commit_parsed_tree (self, subject, body, metadata, root,
+                           &ret_commit_checksum, error))
+    goto out;
+  
+  ret = TRUE;
+ out:
+  if (!ret)
+    {
+      if (ret_commit_checksum)
+        g_checksum_free (ret_commit_checksum);
+    }
+  else
+    {
+      *out_commit = ret_commit_checksum;
+    }
+  if (previous_commit)
+    g_variant_unref (previous_commit);
+  parsed_directory_data_free (root);
+  if (root_metadata)
+    g_variant_unref (root_metadata);
+  if (root_meta_checksum)
+    g_checksum_free (root_meta_checksum);
+  return ret;
+}
+
+gboolean      
+ostree_repo_commit_from_filelist_fd (OstreeRepo *self,
+                                       const char   *subject,
+                                       const char   *body,
+                                       GVariant     *metadata,
+                                       const char   *base,
+                                       int           fd,
+                                       char          separator,
+                                       GChecksum   **out_commit,
+                                       GError      **error)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  gboolean ret = FALSE;
+  ParsedDirectoryData *root = NULL;
+  GVariant *previous_commit = NULL;
+  GChecksum *ret_commit_checksum = NULL;
+  GUnixInputStream *in = NULL;
+  GDataInputStream *datain = NULL;
+  char *filename = NULL;
+  gsize filename_len;
+  GError *temp_error = NULL;
+  GVariant *root_metadata = NULL;
+  GChecksum *root_meta_checksum = NULL;
+
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+  g_return_val_if_fail (priv->inited, FALSE);
+
+  /* We're overwriting the tree */
+  if (!import_root (self, base, &root, error))
+    goto out;
+
+  in = (GUnixInputStream*)g_unix_input_stream_new (fd, FALSE);
+  datain = g_data_input_stream_new ((GInputStream*)in);
+
+  while ((filename = g_data_input_stream_read_upto (datain, &separator, 1,
+                                                    &filename_len, NULL, &temp_error)) != NULL)
+    {
+      if (!g_data_input_stream_read_byte (datain, NULL, &temp_error))
+        {
+          if (temp_error != NULL)
+            {
+              g_propagate_prefixed_error (error, temp_error, "%s", "While reading filelist: ");
+              goto out;
+            }
+        }
+      if (!add_one_path_to_tree_and_import (self, base, filename, root->tree_data, error))
+        goto out;
+      g_free (filename);
+      filename = NULL;
+    }
+  if (filename == NULL && temp_error != NULL)
+    {
+      g_propagate_prefixed_error (error, temp_error, "%s", "While reading filelist: ");
+      goto out;
+    }
+  if (!commit_parsed_tree (self, subject, body, metadata,
+                           root, &ret_commit_checksum, error))
+    goto out;
+  
+  ret = TRUE;
+ out:
+  if (!ret)
+    {
+      if (ret_commit_checksum)
+        g_checksum_free (ret_commit_checksum);
+    }
+  else
+    {
+      *out_commit = ret_commit_checksum;
+    }
+  if (root_metadata)
+    g_variant_unref (root_metadata);
+  if (root_meta_checksum)
+    g_checksum_free (root_meta_checksum);
+  g_clear_object (&datain);
+  g_clear_object (&in);
+  g_free (filename);
+  parsed_directory_data_free (root);
+  return ret;
+  
+}
+
+static gboolean
+iter_object_dir (OstreeRepo   *self,
+                 GFile          *dir,
+                 OstreeRepoObjectIter  callback,
+                 gpointer                user_data,
+                 GError                **error)
+{
+  gboolean ret = FALSE;
+  GError *temp_error = NULL;
+  GFileEnumerator *enumerator = NULL;
+  GFileInfo *file_info = NULL;
+  char *dirpath = NULL;
+
+  dirpath = g_file_get_path (dir);
+
+  enumerator = g_file_enumerate_children (dir, "standard::name,standard::type,unix::*", 
+                                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                          NULL, 
+                                          error);
+  if (!enumerator)
+    goto out;
+  
+  while ((file_info = g_file_enumerator_next_file (enumerator, NULL, &temp_error)) != NULL)
+    {
+      const char *name;
+      guint32 type;
+      name = g_file_info_get_attribute_byte_string (file_info, "standard::name"); 
+      type = g_file_info_get_attribute_uint32 (file_info, "standard::type");
+      
+      if (type != G_FILE_TYPE_DIRECTORY
+          && (g_str_has_suffix (name, ".meta")
+              || g_str_has_suffix (name, ".file")))
+        {
+          char *dot;
+          char *path;
+          
+          dot = strrchr (name, '.');
+          g_assert (dot);
+          
+          if ((dot - name) == 62)
+            {
+              path = g_build_filename (dirpath, name, NULL);
+              callback (self, path, file_info, user_data);
+              g_free (path);
+            }
+        }
+
+      g_object_unref (file_info);
+    }
+  if (file_info == NULL && temp_error != NULL)
+    {
+      g_propagate_error (error, temp_error);
+      goto out;
+    }
+  if (!g_file_enumerator_close (enumerator, NULL, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  g_free (dirpath);
+  return ret;
+}
+
+gboolean
+ostree_repo_iter_objects (OstreeRepo  *self,
+                            OstreeRepoObjectIter callback,
+                            gpointer       user_data,
+                            GError        **error)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  GFile *objectdir = NULL;
+  GFileEnumerator *enumerator = NULL;
+  gboolean ret = FALSE;
+  GFileInfo *file_info = NULL;
+  GError *temp_error = NULL;
+
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+  g_return_val_if_fail (priv->inited, FALSE);
+
+  objectdir = ot_util_new_file_for_path (priv->objects_path);
+  enumerator = g_file_enumerate_children (objectdir, "standard::name,standard::type,unix::*", 
+                                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                          NULL, 
+                                          error);
+  if (!enumerator)
+    goto out;
+
+  while ((file_info = g_file_enumerator_next_file (enumerator, NULL, &temp_error)) != NULL)
+    {
+      const char *name;
+      guint32 type;
+
+      name = g_file_info_get_attribute_byte_string (file_info, "standard::name"); 
+      type = g_file_info_get_attribute_uint32 (file_info, "standard::type");
+      
+      if (strlen (name) == 2 && type == G_FILE_TYPE_DIRECTORY)
+        {
+          GFile *objdir = g_file_get_child (objectdir, name);
+          if (!iter_object_dir (self, objdir, callback, user_data, error))
+            {
+              g_object_unref (objdir);
+              goto out;
+            }
+          g_object_unref (objdir);
+        }
+      g_object_unref (file_info);
+    }
+  if (file_info == NULL && temp_error != NULL)
+    {
+      g_propagate_error (error, temp_error);
+      goto out;
+    }
+  if (!g_file_enumerator_close (enumerator, NULL, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  g_clear_object (&file_info);
+  g_clear_object (&enumerator);
+  g_clear_object (&objectdir);
+  return ret;
+}
+
+gboolean
+ostree_repo_load_variant (OstreeRepo *repo,
+                            const char   *sha256,
+                            OstreeSerializedVariantType *out_type,
+                            GVariant    **out_variant,
+                            GError      **error)
+{
+  gboolean ret = FALSE;
+  OstreeSerializedVariantType ret_type;
+  GVariant *ret_variant = NULL;
+
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+  
+  if (!load_gvariant_object_unknown (repo, sha256, &ret_type, &ret_variant, error))
+    goto out;
+
+  ret = TRUE;
+  *out_type = ret_type;
+  *out_variant = ret_variant;
+ out:
+  if (!ret)
+    {
+      if (ret_variant)
+        g_variant_unref (ret_variant);
+      g_prefix_error (error, "Failed to load metadata variant '%s': ", sha256);
+    }
+  return ret;
+}
+
+const char *
+ostree_repo_get_head (OstreeRepo  *self)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+
+  g_return_val_if_fail (priv->inited, NULL);
+
+  return priv->current_head;
+}
+
+static gboolean
+resolve_ref (OstreeRepo *self,
+             const char   *ref,
+             char       **resolved,
+             GError      **error)
+{
+  if (strcmp (ref, "HEAD") == 0)
+    {
+      *resolved = g_strdup (ostree_repo_get_head (self));
+      return TRUE;
+    }
+  else if (strlen (ref) == 64)
+    {
+      *resolved = g_strdup (ref);
+      return TRUE;
+    }
+  g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+               "Invalid ref '%s' (must be SHA256 or HEAD)", ref);
+  return FALSE;
+}
+
+static gboolean
+checkout_tree (OstreeRepo    *self,
+               ParsedTreeData  *tree,
+               const char      *destination,
+               GError         **error);
+
+static gboolean
+checkout_one_directory (OstreeRepo  *self,
+                        const char *destination,
+                        const char *dirname,
+                        ParsedDirectoryData *dir,
+                        GError         **error)
+{
+  gboolean ret = FALSE;
+  char *dest_path = NULL;
+  guint32 version, uid, gid, mode;
+  GVariant *xattr_variant = NULL;
+  const guint8 *xattrs = NULL;
+  gsize xattr_len;
+
+  dest_path = g_build_filename (destination, dirname, NULL);
+      
+  g_variant_get (dir->meta_data, "(uuuu@a(ayay))",
+                 &version, &uid, &gid, &mode,
+                 &xattr_variant);
+
+  if (mkdir (dest_path, (mode_t)mode) < 0)
+    {
+      ot_util_set_error_from_errno (error, errno);
+      g_prefix_error (error, "Failed to create directory '%s': ", dest_path);
+      goto out;
+    }
+      
+  if (!checkout_tree (self, dir->tree_data, dest_path, error))
+    goto out;
+
+  /* TODO - xattrs */
+      
+  ret = TRUE;
+ out:
+  g_free (dest_path);
+  g_variant_unref (xattr_variant);
+  return ret;
+}
+
+static gboolean
+checkout_tree (OstreeRepo    *self,
+               ParsedTreeData  *tree,
+               const char      *destination,
+               GError         **error)
+{
+  gboolean ret = FALSE;
+  GHashTableIter hash_iter;
+  gpointer key, value;
+
+  g_hash_table_iter_init (&hash_iter, tree->files);
+  while (g_hash_table_iter_next (&hash_iter, &key, &value))
+    {
+      const char *filename = key;
+      const char *checksum = value;
+      char *object_path;
+      char *dest_path;
+
+      object_path = get_object_path (self, checksum, OSTREE_OBJECT_TYPE_FILE);
+      dest_path = g_build_filename (destination, filename, NULL);
+      if (link (object_path, dest_path) < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          g_free (object_path);
+          g_free (dest_path);
+          goto out;
+        }
+      g_free (object_path);
+      g_free (dest_path);
+    }
+
+  g_hash_table_iter_init (&hash_iter, tree->directories);
+  while (g_hash_table_iter_next (&hash_iter, &key, &value))
+    {
+      const char *dirname = key;
+      ParsedDirectoryData *dir = value;
+      
+      if (!checkout_one_directory (self, destination, dirname, dir, error))
+        goto out;
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+gboolean
+ostree_repo_checkout (OstreeRepo *self,
+                        const char   *ref,
+                        const char   *destination,
+                        GError      **error)
+{
+  gboolean ret = FALSE;
+  GVariant *commit = NULL;
+  char *resolved = NULL;
+  char *root_meta_sha = NULL;
+  ParsedDirectoryData *root = NULL;
+
+  if (g_file_test (destination, G_FILE_TEST_EXISTS))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Destination path '%s' already exists",
+                   destination);
+      goto out;
+    }
+
+  if (!resolve_ref (self, ref, &resolved, error))
+    goto out;
+
+  if (!load_commit_and_trees (self, resolved, &commit, &root, error))
+    goto out;
+
+  if (!checkout_one_directory (self, destination, NULL, root, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  g_free (resolved);
+  if (commit)
+    g_variant_unref (commit);
+  parsed_directory_data_free (root);
+  g_free (root_meta_sha);
+  return ret;
+}
diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h
new file mode 100644 (file)
index 0000000..e2b9de3
--- /dev/null
@@ -0,0 +1,105 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+/* ostree-repo.h */
+
+#ifndef _OSTREE_REPO
+#define _OSTREE_REPO
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define OSTREE_TYPE_REPO ostree_repo_get_type()
+#define OSTREE_REPO(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), OSTREE_TYPE_REPO, OstreeRepo))
+#define OSTREE_REPO_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), OSTREE_TYPE_REPO, OstreeRepoClass))
+#define OSTREE_IS_REPO(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), OSTREE_TYPE_REPO))
+#define OSTREE_IS_REPO_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), OSTREE_TYPE_REPO))
+#define OSTREE_REPO_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), OSTREE_TYPE_REPO, OstreeRepoClass))
+
+typedef struct {
+  GObject parent;
+} OstreeRepo;
+
+typedef struct {
+  GObjectClass parent_class;
+} OstreeRepoClass;
+
+GType ostree_repo_get_type (void);
+
+OstreeRepo* ostree_repo_new (const char *path);
+
+gboolean      ostree_repo_check (OstreeRepo  *self, GError **error);
+
+gboolean      ostree_repo_link_file (OstreeRepo *self,
+                                       const char   *path,
+                                       gboolean      ignore_exists,
+                                       gboolean      force,
+                                       GError      **error);
+
+const char *  ostree_repo_get_head (OstreeRepo  *self);
+
+gboolean      ostree_repo_load_variant (OstreeRepo *self,
+                                          const char   *sha256,
+                                          OstreeSerializedVariantType *out_type,
+                                          GVariant    **out_variant,
+                                          GError      **error);
+
+gboolean      ostree_repo_commit (OstreeRepo *self,
+                                    const char   *subject,
+                                    const char   *body,
+                                    GVariant     *metadata,
+                                    const char   *base,
+                                    GPtrArray    *modified_files,
+                                    GPtrArray    *removed_files,
+                                    GChecksum   **out_commit,
+                                    GError      **error);
+
+gboolean      ostree_repo_commit_from_filelist_fd (OstreeRepo *self,
+                                                     const char   *subject,
+                                                     const char   *body,
+                                                     GVariant     *metadata,
+                                                     const char   *base,
+                                                     int           fd,
+                                                     char          separator,
+                                                     GChecksum   **out_commit,
+                                                     GError      **error);
+
+gboolean      ostree_repo_checkout (OstreeRepo *self,
+                                      const char   *ref,
+                                      const char   *destination,
+                                      GError      **error);
+
+typedef void (*OstreeRepoObjectIter) (OstreeRepo *self, const char *path,
+                                        GFileInfo *fileinfo, gpointer user_data);
+
+gboolean     ostree_repo_iter_objects (OstreeRepo  *self,
+                                         OstreeRepoObjectIter callback,
+                                         gpointer       user_data,
+                                         GError        **error);
+
+G_END_DECLS
+
+#endif /* _OSTREE_REPO */
diff --git a/src/libostree/ostree-types.h b/src/libostree/ostree-types.h
new file mode 100644 (file)
index 0000000..5b306b3
--- /dev/null
@@ -0,0 +1,33 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#ifndef __OSTREE_TYPES_H__
+#define __OSTREE_TYPES_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define OSTREE_REPO_DIR ".ot"
+
+G_END_DECLS
+
+#endif
diff --git a/src/libostree/ostree.h b/src/libostree/ostree.h
new file mode 100644 (file)
index 0000000..75cced5
--- /dev/null
@@ -0,0 +1,28 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#ifndef __OSTREE_H__
+
+#include <ostree-core.h>
+#include <ostree-repo.h>
+#include <ostree-types.h>
+
+#endif
diff --git a/src/libotutil/ot-gio-utils.c b/src/libotutil/ot-gio-utils.c
new file mode 100644 (file)
index 0000000..0d49075
--- /dev/null
@@ -0,0 +1,114 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include <glib-unix.h>
+#include <gio/gio.h>
+#include <gio/gunixinputstream.h>
+
+#include <string.h>
+
+#include "otutil.h"
+
+gboolean
+ot_util_ensure_directory (const char *path, gboolean with_parents, GError **error)
+{
+  GFile *dir;
+  GError *temp_error = NULL;
+  gboolean ret = FALSE;
+
+  dir = g_file_new_for_path (path);
+  if (with_parents)
+    ret = g_file_make_directory_with_parents (dir, NULL, &temp_error);
+  else
+    ret = g_file_make_directory (dir, NULL, &temp_error);
+  if (!ret)
+    {
+      if (!g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_EXISTS))
+        {
+          g_propagate_error (error, temp_error);
+          goto out;
+        }
+      else
+        g_clear_error (&temp_error);
+    }
+
+  ret = TRUE;
+ out:
+  g_clear_object (&dir);
+  return ret;
+}
+
+
+char *
+ot_util_get_file_contents_utf8 (const char *path,
+                                GError    **error)
+{
+  char *contents;
+  gsize len;
+  if (!g_file_get_contents (path, &contents, &len, error))
+    return NULL;
+  if (!g_utf8_validate (contents, len, NULL))
+    {
+      g_free (contents);
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_FAILED,
+                   "File %s contains invalid UTF-8",
+                   path);
+      return NULL;
+    }
+  return contents;
+}
+
+GInputStream *
+ot_util_read_file_noatime (GFile *file, GCancellable *cancellable, GError **error)
+{
+  GInputStream *ret = NULL;
+  int fd;
+  int flags = O_RDONLY;
+  char *path = NULL;
+
+  path = g_file_get_path (file);
+#ifdef O_NOATIME
+  flags |= O_NOATIME;
+#endif
+  fd = open (path, flags);
+  if (fd < 0)
+    {
+      ot_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+
+  ret = (GInputStream*)g_unix_input_stream_new (fd, TRUE);
+  
+ out:
+  g_free (path);
+  return ret;
+}
+
+/* Like g_file_new_for_path, but only do local stuff, not GVFS */
+GFile *
+ot_util_new_file_for_path (const char *path)
+{
+  return g_vfs_get_file_for_path (g_vfs_get_local (), path);
+}
diff --git a/src/libotutil/ot-gio-utils.h b/src/libotutil/ot-gio-utils.h
new file mode 100644 (file)
index 0000000..722e00c
--- /dev/null
@@ -0,0 +1,39 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#ifndef __OSTREE_GIO_UTILS_H__
+#define __OSTREE_GIO_UTILS_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+GFile *ot_util_new_file_for_path (const char *path);
+
+gboolean ot_util_ensure_directory (const char *path, gboolean with_parents, GError **error);
+
+char * ot_util_get_file_contents_utf8 (const char *path, GError    **error);
+
+GInputStream *ot_util_read_file_noatime (GFile *file, GCancellable *cancellable, GError **error);
+
+G_END_DECLS
+
+#endif
diff --git a/src/libotutil/ot-unix-utils.c b/src/libotutil/ot-unix-utils.c
new file mode 100644 (file)
index 0000000..95cdb8d
--- /dev/null
@@ -0,0 +1,257 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include "ot-unix-utils.h"
+
+#include <glib-unix.h>
+#include <gio/gio.h>
+#include <gio/gunixoutputstream.h>
+
+#include <string.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+gboolean
+ot_util_spawn_pager (GOutputStream  **out_stream,
+                     GError         **error)
+{
+  const char *pager;
+  char *argv[2];
+  int stdin_fd;
+  pid_t pid;
+  gboolean ret = FALSE;
+  GOutputStream *ret_stream = NULL;
+
+  if (!isatty (1))
+    {
+      ret_stream = (GOutputStream*)g_unix_output_stream_new (1, TRUE);
+    }
+  else
+    {
+      pager = g_getenv ("GIT_PAGER");
+      if (pager == NULL)
+        pager = "less";
+      
+      argv[0] = (char*)pager;
+      argv[1] = NULL;
+      
+      if (!g_spawn_async_with_pipes (NULL, argv, NULL, G_SPAWN_SEARCH_PATH,
+                                     NULL, NULL, &pid, &stdin_fd, NULL, NULL, error))
+        {
+          g_prefix_error (error, "%s", "Failed to spawn pager: ");
+          goto out;
+        }
+      
+      ret_stream = (GOutputStream*)g_unix_output_stream_new (stdin_fd, TRUE);
+    }
+
+  *out_stream = ret_stream;
+  ret_stream = NULL;
+  ret = TRUE;
+ out:
+  g_clear_object (&ret_stream);
+  return ret;
+}
+
+static int
+compare_filenames_by_component_length (const char *a,
+                                       const char *b)
+{
+  char *a_slash, *b_slash;
+
+  a_slash = strchr (a, '/');
+  b_slash = strchr (b, '/');
+  while (a_slash && b_slash)
+    {
+      a = a_slash + 1;
+      b = b_slash + 1;
+      a_slash = strchr (a, '/');
+      b_slash = strchr (b, '/');
+    }
+  if (a_slash)
+    return -1;
+  else if (b_slash)
+    return 1;
+  else
+    return 0;
+}
+
+GPtrArray *
+ot_util_sort_filenames_by_component_length (GPtrArray *files)
+{
+  GPtrArray *array = g_ptr_array_sized_new (files->len);
+  memcpy (array->pdata, files->pdata, sizeof (gpointer) * files->len);
+  g_ptr_array_sort (array, (GCompareFunc) compare_filenames_by_component_length);
+  return array;
+}
+
+int
+ot_util_count_filename_components (const char *path)
+{
+  int i = 0;
+
+  while (path)
+    {
+      i++;
+      path = strchr (path, '/');
+      if (path)
+        path++;
+    }
+  return i;
+}
+
+gboolean
+ot_util_filename_has_dotdot (const char *path)
+{
+  char *p;
+  char last;
+
+  if (strcmp (path, "..") == 0)
+    return TRUE;
+  if (g_str_has_prefix (path, "../"))
+    return TRUE;
+  p = strstr (path, "/..");
+  if (!p)
+    return FALSE;
+  last = *(p + 1);
+  return last == '\0' || last == '/';
+}
+
+GPtrArray *
+ot_util_path_split (const char *path)
+{
+  GPtrArray *ret = NULL;
+  const char *p;
+  const char *slash;
+  int i;
+
+  g_return_val_if_fail (path[0] != '/', NULL);
+
+  ret = g_ptr_array_new ();
+  g_ptr_array_set_free_func (ret, g_free);
+
+  p = path;
+  do {
+    slash = strchr (p, '/');
+    if (!slash)
+      {
+        g_ptr_array_add (ret, g_strdup (p));
+        p = NULL;
+      }
+    else
+      {
+        g_ptr_array_add (ret, g_strndup (p, slash - p));
+        p = slash + 1;
+      }
+  } while (p && *p);
+
+  /* Canonicalize by removing duplicate '.' */
+  for (i = ret->len-1; i >= 0; i--)
+    {
+      if (strcmp (ret->pdata[i], ".") == 0)
+        g_ptr_array_remove_index (ret, i);
+    }
+
+  return ret;
+}
+
+char *
+ot_util_path_join_n (const char *base, GPtrArray *components, int n)
+{
+  int max = MIN(n+1, components->len);
+  GPtrArray *subcomponents;
+  char *path;
+  int i;
+
+  subcomponents = g_ptr_array_new ();
+
+  if (base != NULL)
+    g_ptr_array_add (subcomponents, (char*)base);
+
+  for (i = 0; i < max; i++)
+    {
+      g_ptr_array_add (subcomponents, components->pdata[i]);
+    }
+  g_ptr_array_add (subcomponents, NULL);
+  
+  path = g_build_filenamev ((char**)subcomponents->pdata);
+  g_ptr_array_free (subcomponents, TRUE);
+  
+  return path;
+}
+
+void
+ot_util_set_error_from_errno (GError **error,
+                              gint     saved_errno)
+{
+  g_set_error_literal (error,
+                       G_UNIX_ERROR,
+                       0,
+                       g_strerror (saved_errno));
+  errno = saved_errno;
+}
+
+int
+ot_util_open_file_read (const char *path, GError **error)
+{
+  char *dirname = NULL;
+  char *basename = NULL;
+  DIR *dir = NULL;
+  int fd = -1;
+
+  dirname = g_path_get_dirname (path);
+  basename = g_path_get_basename (path);
+  dir = opendir (dirname);
+  if (dir == NULL)
+    {
+      ot_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+
+  fd = ot_util_open_file_read_at (dirfd (dir), basename, error);
+
+ out:
+  g_free (basename);
+  g_free (dirname);
+  if (dir != NULL)
+    closedir (dir);
+  return fd;
+}
+
+int
+ot_util_open_file_read_at (int dirfd, const char *name, GError **error)
+{
+  int fd;
+  int flags = O_RDONLY;
+  
+#ifdef O_CLOEXEC
+  flags |= O_CLOEXEC;
+#endif
+#ifdef O_NOATIME
+  flags |= O_NOATIME;
+#endif
+  fd = openat (dirfd, name, flags);
+  if (fd < 0)
+    ot_util_set_error_from_errno (error, errno);
+  return fd;
+}
diff --git a/src/libotutil/ot-unix-utils.h b/src/libotutil/ot-unix-utils.h
new file mode 100644 (file)
index 0000000..dbbf8c0
--- /dev/null
@@ -0,0 +1,59 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#ifndef __OSTREE_UNIX_UTILS_H__
+#define __OSTREE_UNIX_UTILS_H__
+
+#include <gio/gio.h>
+#include <glib-unix.h>
+
+/* I just put all this shit here. Sue me. */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdio.h>
+
+G_BEGIN_DECLS
+
+gboolean ot_util_spawn_pager (GOutputStream  **out_stream, GError         **error);
+
+gboolean ot_util_filename_has_dotdot (const char *path);
+
+GPtrArray *ot_util_sort_filenames_by_component_length (GPtrArray *files);
+
+GPtrArray* ot_util_path_split (const char *path);
+
+char *ot_util_path_join_n (const char *base, GPtrArray *components, int n);
+
+int ot_util_count_filename_components (const char *path);
+
+int ot_util_open_file_read (const char *path, GError **error);
+
+int ot_util_open_file_read_at (int dirfd, const char *name, GError **error);
+
+void ot_util_set_error_from_errno (GError **error, gint saved_errno);
+
+G_END_DECLS
+
+#endif
diff --git a/src/libotutil/otutil.h b/src/libotutil/otutil.h
new file mode 100644 (file)
index 0000000..38cfd8e
--- /dev/null
@@ -0,0 +1,27 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#ifndef __OSTREE_UTIL_H__
+
+#include <ot-unix-utils.h>
+#include <ot-gio-utils.h>
+
+#endif
index 6e8ce9dc82841285b7cd2138e606ee8d7b0dfeb6..d6e0b5e585c26245e84b1d503cf71db57bdc5a54 100644 (file)
 
 #include <string.h>
 
-#include "ht-builtins.h"
-
-static HacktreeBuiltin builtins[] = {
-  { "checkout", hacktree_builtin_checkout, 0 },
-  { "init", hacktree_builtin_init, 0 },
-  { "commit", hacktree_builtin_commit, 0 },
-  { "link-file", hacktree_builtin_link_file, 0 },
-  { "log", hacktree_builtin_log, 0 },
-  { "fsck", hacktree_builtin_fsck, 0 },
-  { "show", hacktree_builtin_show, 0 },
+#include "ot-builtins.h"
+
+static OstreeBuiltin builtins[] = {
+  { "checkout", ostree_builtin_checkout, 0 },
+  { "init", ostree_builtin_init, 0 },
+  { "commit", ostree_builtin_commit, 0 },
+  { "link-file", ostree_builtin_link_file, 0 },
+  { "log", ostree_builtin_log, 0 },
+  { "fsck", ostree_builtin_fsck, 0 },
+  { "show", ostree_builtin_show, 0 },
   { NULL }
 };
 
 static int
 usage (char **argv, gboolean is_error)
 {
-  HacktreeBuiltin *builtin = builtins;
+  OstreeBuiltin *builtin = builtins;
   void (*print_func) (const gchar *format, ...);
 
   if (is_error)
@@ -67,7 +67,7 @@ int
 main (int    argc,
       char **argv)
 {
-  HacktreeBuiltin *builtin;
+  OstreeBuiltin *builtin;
   const char *cmd;
 
   g_type_init ();
diff --git a/src/ot-builtin-checkout.c b/src/ot-builtin-checkout.c
new file mode 100644 (file)
index 0000000..8eb4353
--- /dev/null
@@ -0,0 +1,81 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include "ot-builtins.h"
+#include "ostree.h"
+
+#include <glib/gi18n.h>
+
+static char *repo_path;
+
+static GOptionEntry options[] = {
+  { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", "repo" },
+  { NULL }
+};
+
+gboolean
+ostree_builtin_checkout (int argc, char **argv, const char *prefix, GError **error)
+{
+  GOptionContext *context;
+  gboolean ret = FALSE;
+  OstreeRepo *repo = NULL;
+  int i;
+  const char *commit;
+  const char *destination;
+
+  context = g_option_context_new ("COMMIT DESTINATION - Check out a commit into a filesystem tree");
+  g_option_context_add_main_entries (context, options, NULL);
+
+  if (!g_option_context_parse (context, &argc, &argv, error))
+    goto out;
+
+  if (repo_path == NULL)
+    repo_path = ".";
+
+  repo = ostree_repo_new (repo_path);
+  if (!ostree_repo_check (repo, error))
+    goto out;
+
+  if (argc < 3)
+    {
+      gchar *help = g_option_context_get_help (context, TRUE, NULL);
+      g_printerr ("%s\n", help);
+      g_free (help);
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "COMMIT and DESTINATION must be specified");
+      goto out;
+    }
+
+  commit = argv[1];
+  destination = argv[2];
+
+  if (!ostree_repo_checkout (repo, commit, destination, error))
+    goto out;
+  ret = TRUE;
+ out:
+  if (context)
+    g_option_context_free (context);
+  g_clear_object (&repo);
+  return ret;
+}
diff --git a/src/ot-builtin-commit.c b/src/ot-builtin-commit.c
new file mode 100644 (file)
index 0000000..241ba2b
--- /dev/null
@@ -0,0 +1,165 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include "ot-builtins.h"
+#include "ostree.h"
+
+#include <glib/gi18n.h>
+
+static char *repo_path;
+static gboolean separator_null;
+static int from_fd = -1;
+static gboolean from_stdin;
+static char *from_file;
+static char *subject;
+static char *body;
+static char **additions;
+static char **removals;
+
+static GOptionEntry options[] = {
+  { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", "repo" },
+  { "subject", 's', 0, G_OPTION_ARG_STRING, &subject, "One line subject", "subject" },
+  { "body", 'b', 0, G_OPTION_ARG_STRING, &body, "Full description", "body" },
+  { "from-fd", 0, 0, G_OPTION_ARG_INT, &from_fd, "Read new tree files from fd", "file descriptor" },
+  { "from-stdin", 0, 0, G_OPTION_ARG_NONE, &from_stdin, "Read new tree files from stdin", "file descriptor" },
+  { "from-file", 0, 0, G_OPTION_ARG_FILENAME, &from_file, "Read new tree files from another file", "path" },
+  { "separator-null", 0, 0, G_OPTION_ARG_NONE, &separator_null, "", "Use '\\0' as filename separator, as with find -print0" },
+  { "add", 'a', 0, G_OPTION_ARG_FILENAME_ARRAY, &additions, "Relative file path to add", "filename" },
+  { "remove", 'r', 0, G_OPTION_ARG_FILENAME_ARRAY, &removals, "Relative file path to remove", "filename" },
+  { NULL }
+};
+
+gboolean
+ostree_builtin_commit (int argc, char **argv, const char *prefix, GError **error)
+{
+  GOptionContext *context;
+  gboolean ret = FALSE;
+  OstreeRepo *repo = NULL;
+  gboolean using_filename_cmdline;
+  gboolean using_filedescriptors;
+  GPtrArray *additions_array = NULL;
+  GPtrArray *removals_array = NULL;
+  GChecksum *commit_checksum = NULL;
+  char **iter;
+
+  context = g_option_context_new ("- Commit a new revision");
+  g_option_context_add_main_entries (context, options, NULL);
+
+  if (!g_option_context_parse (context, &argc, &argv, error))
+    goto out;
+
+  if (repo_path == NULL)
+    repo_path = ".";
+  if (prefix == NULL)
+    prefix = ".";
+
+  repo = ostree_repo_new (repo_path);
+  if (!ostree_repo_check (repo, error))
+    goto out;
+
+  using_filename_cmdline = (removals || additions);
+  using_filedescriptors = (from_file || from_fd >= 0 || from_stdin);
+
+  if (!(using_filename_cmdline || using_filedescriptors))
+    {
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "No additions or removals specified");
+      goto out;
+    }
+  if (using_filename_cmdline && using_filedescriptors)
+    {
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "File descriptors may not be combined with --add or --remove");
+      goto out;
+    }
+
+  if (!subject)
+    {
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "A subject must be specified with --subject");
+      goto out;
+    }
+
+  if (using_filename_cmdline)
+    {
+      g_assert (removals || additions);
+      additions_array = g_ptr_array_new ();
+      removals_array = g_ptr_array_new ();
+
+      if (additions)
+        for (iter = additions; *iter; iter++)
+          g_ptr_array_add (additions_array, *iter);
+      if (removals)
+        for (iter = removals; *iter; iter++)
+          g_ptr_array_add (removals_array, *iter);
+      
+      if (!ostree_repo_commit (repo, subject, body, NULL,
+                                 prefix, additions_array,
+                                 removals_array,
+                                 &commit_checksum,
+                                 error))
+        goto out;
+    }
+  else if (using_filedescriptors)
+    {
+      char separator = separator_null ? '\0' : '\n';
+      gboolean temp_fd = -1;
+
+      if (from_stdin)
+        from_fd = 0;
+      else if (from_file)
+        {
+          temp_fd = ot_util_open_file_read (from_file, error);
+          if (temp_fd < 0)
+            {
+              g_prefix_error (error, "Failed to open '%s': ", from_file);
+              goto out;
+            }
+          from_fd = temp_fd;
+        }
+      if (!ostree_repo_commit_from_filelist_fd (repo, subject, body, NULL,
+                                                  prefix, from_fd, separator,
+                                                  &commit_checksum, error))
+        {
+          if (temp_fd >= 0)
+            close (temp_fd);
+          goto out;
+        }
+      if (temp_fd >= 0)
+        close (temp_fd);
+    }
+  ret = TRUE;
+  g_print ("%s\n", g_checksum_get_string (commit_checksum));
+ out:
+  if (context)
+    g_option_context_free (context);
+  g_clear_object (&repo);
+  if (removals_array)
+    g_ptr_array_free (removals_array, TRUE);
+  if (additions_array)
+    g_ptr_array_free (additions_array, TRUE);
+  if (commit_checksum)
+    g_checksum_free (commit_checksum);
+  return ret;
+}
diff --git a/src/ot-builtin-fsck.c b/src/ot-builtin-fsck.c
new file mode 100644 (file)
index 0000000..e85c9f6
--- /dev/null
@@ -0,0 +1,140 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include "ot-builtins.h"
+#include "ostree.h"
+
+#include <glib/gi18n.h>
+
+static char *repo_path;
+static gboolean quiet;
+
+static GOptionEntry options[] = {
+  { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", NULL },
+  { "quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, "Don't display informational messages", NULL },
+  { NULL }
+};
+
+typedef struct {
+  guint n_objects;
+} HtFsckData;
+
+static void
+object_iter_callback (OstreeRepo  *repo,
+                      const char    *path,
+                      GFileInfo     *file_info,
+                      gpointer       user_data)
+{
+  HtFsckData *data = user_data;
+  struct stat stbuf;
+  GChecksum *checksum = NULL;
+  GError *error = NULL;
+  char *dirname = NULL;
+  char *checksum_prefix = NULL;
+  char *checksum_string = NULL;
+  char *filename_checksum = NULL;
+  char *dot;
+
+  dirname = g_path_get_dirname (path);
+  checksum_prefix = g_path_get_basename (dirname);
+  
+  /* nlinks = g_file_info_get_attribute_uint32 (file_info, "unix::nlink");
+     if (nlinks < 2 && !quiet)
+     g_printerr ("note: floating object: %s\n", path); */
+
+  if (!ostree_stat_and_checksum_file (-1, path, &checksum, &stbuf, &error))
+    goto out;
+
+  filename_checksum = g_strdup (g_file_info_get_name (file_info));
+  dot = strrchr (filename_checksum, '.');
+  g_assert (dot != NULL);
+  *dot = '\0';
+
+  checksum_string = g_strconcat (checksum_prefix, filename_checksum, NULL);
+
+  if (strcmp (checksum_string, g_checksum_get_string (checksum)) != 0)
+    {
+      g_printerr ("ERROR: corrupted object '%s' expected checksum: %s\n",
+                  path, g_checksum_get_string (checksum));
+    }
+
+  data->n_objects++;
+
+ out:
+  if (checksum != NULL)
+    g_checksum_free (checksum);
+  g_free (dirname);
+  g_free (checksum_prefix);
+  g_free (checksum_string);
+  g_free (filename_checksum);
+  if (error != NULL)
+    {
+      g_printerr ("%s\n", error->message);
+      g_clear_error (&error);
+    }
+}
+
+gboolean
+ostree_builtin_fsck (int argc, char **argv, const char *prefix, GError **error)
+{
+  GOptionContext *context;
+  HtFsckData data;
+  gboolean ret = FALSE;
+  OstreeRepo *repo = NULL;
+  const char *head;
+
+  context = g_option_context_new ("- Check the repository for consistency");
+  g_option_context_add_main_entries (context, options, NULL);
+
+  if (!g_option_context_parse (context, &argc, &argv, error))
+    goto out;
+
+  if (repo_path == NULL)
+    repo_path = ".";
+
+  data.n_objects = 0;
+
+  repo = ostree_repo_new (repo_path);
+  if (!ostree_repo_check (repo, error))
+    goto out;
+
+  if (!ostree_repo_iter_objects (repo, object_iter_callback, &data, error))
+    goto out;
+
+  head = ostree_repo_get_head (repo);
+  if (!head)
+    {
+      if (!quiet)
+        g_printerr ("No HEAD file\n");
+    }
+
+  if (!quiet)
+    g_printerr ("Total Objects: %u\n", data.n_objects);
+
+  ret = TRUE;
+ out:
+  if (context)
+    g_option_context_free (context);
+  g_clear_object (&repo);
+  return ret;
+}
diff --git a/src/ot-builtin-init.c b/src/ot-builtin-init.c
new file mode 100644 (file)
index 0000000..9d86464
--- /dev/null
@@ -0,0 +1,72 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include "ot-builtins.h"
+#include "ostree.h"
+
+#include <glib/gi18n.h>
+
+static char *repo_path;
+static GOptionEntry options[] = {
+  { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", NULL },
+  { NULL }
+};
+
+gboolean
+ostree_builtin_init (int argc, char **argv, const char *prefix, GError **error)
+{
+  GOptionContext *context = NULL;
+  gboolean ret = FALSE;
+  char *htdir_path = NULL;
+  char *objects_path = NULL;
+  GFile *htdir = NULL;
+  GFile *objects_dir = NULL;
+
+  context = g_option_context_new ("- Check the repository for consistency");
+  g_option_context_add_main_entries (context, options, NULL);
+
+  if (!g_option_context_parse (context, &argc, &argv, error))
+    goto out;
+
+  if (repo_path == NULL)
+    repo_path = ".";
+
+  htdir_path = g_build_filename (repo_path, OSTREE_REPO_DIR, NULL);
+  htdir = ot_util_new_file_for_path (htdir_path);
+
+  if (!g_file_make_directory (htdir, NULL, error))
+    goto out;
+
+  objects_path = g_build_filename (htdir_path, "objects", NULL);
+  objects_dir = g_file_new_for_path (objects_path);
+  if (!g_file_make_directory (objects_dir, NULL, error))
+    goto out;
+  ret = TRUE;
+ out:
+  if (context)
+    g_option_context_free (context);
+  g_free (htdir_path);
+  g_clear_object (&htdir);
+  return ret;
+}
diff --git a/src/ot-builtin-link-file.c b/src/ot-builtin-link-file.c
new file mode 100644 (file)
index 0000000..dddec44
--- /dev/null
@@ -0,0 +1,73 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include "ot-builtins.h"
+#include "ostree.h"
+
+#include <glib/gi18n.h>
+
+static char *repo_path;
+static gboolean ignore_exists;
+static gboolean force;
+
+static GOptionEntry options[] = {
+  { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", "repo" },
+  { "ignore-exists", 'n', 0, G_OPTION_ARG_NONE, &ignore_exists, "Don't error if file exists", NULL },
+  { "force", 'f', 0, G_OPTION_ARG_NONE, &force, "If object exists, relink file", NULL },
+  { NULL }
+};
+
+gboolean
+ostree_builtin_link_file (int argc, char **argv, const char *prefix, GError **error)
+{
+  GOptionContext *context;
+  gboolean ret = FALSE;
+  OstreeRepo *repo = NULL;
+  int i;
+
+  context = g_option_context_new ("- Create a new hard link in the repository");
+  g_option_context_add_main_entries (context, options, NULL);
+
+  if (!g_option_context_parse (context, &argc, &argv, error))
+    goto out;
+
+  if (repo_path == NULL)
+    repo_path = ".";
+
+  repo = ostree_repo_new (repo_path);
+  if (!ostree_repo_check (repo, error))
+    goto out;
+
+  for (i = 0; i < argc-1; i++)
+    {
+      if (!ostree_repo_link_file (repo, argv[i+1], ignore_exists, force, error))
+        goto out;
+    }
+  ret = TRUE;
+ out:
+  if (context)
+    g_option_context_free (context);
+  g_clear_object (&repo);
+  return ret;
+}
diff --git a/src/ot-builtin-log.c b/src/ot-builtin-log.c
new file mode 100644 (file)
index 0000000..47d5db5
--- /dev/null
@@ -0,0 +1,152 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include "ot-builtins.h"
+#include "ostree.h"
+
+#include <glib/gi18n.h>
+
+static char *repo_path;
+
+static GOptionEntry options[] = {
+  { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", "repo" },
+  { NULL }
+};
+
+gboolean
+ostree_builtin_log (int argc, char **argv, const char *prefix, GError **error)
+{
+  GOptionContext *context;
+  gboolean ret = FALSE;
+  OstreeRepo *repo = NULL;
+  GOutputStream *pager = NULL;
+  GVariant *commit = NULL;
+  char *head;
+
+  context = g_option_context_new ("- Show revision log");
+  g_option_context_add_main_entries (context, options, NULL);
+
+  if (!g_option_context_parse (context, &argc, &argv, error))
+    goto out;
+
+  if (repo_path == NULL)
+    repo_path = ".";
+  if (prefix == NULL)
+    prefix = ".";
+
+  repo = ostree_repo_new (repo_path);
+  if (!ostree_repo_check (repo, error))
+    goto out;
+
+  head = g_strdup (ostree_repo_get_head (repo));
+  if (!head)
+    {
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "No HEAD exists");
+      goto out;
+    }
+
+  if (!ot_util_spawn_pager (&pager, error))
+    goto out;
+
+  while (TRUE)
+    {
+      OstreeSerializedVariantType type;
+      char *formatted = NULL;
+      guint32 version;
+      const char *parent;
+      const char *subject;
+      const char *body;
+      guint64 timestamp;
+      const char *contents;
+      const char *root_metadata;
+      GDateTime *time_obj = NULL;
+      char *formatted_date = NULL;
+      const char *body_newline;
+      gsize bytes_written;
+      GVariant *commit_metadata = NULL;
+      char *formatted_metadata = NULL;
+      
+      if (commit)
+        g_variant_unref (commit);
+      if (!ostree_repo_load_variant (repo, head, &type, &commit, error))
+        goto out;
+
+      /* Ignore commit metadata for now */
+      g_variant_get (commit, "(u@a{sv}&s&s&st&s&s)",
+                     &version, &commit_metadata, &parent, &subject, &body,
+                     &timestamp, &contents, &root_metadata);
+      time_obj = g_date_time_new_from_unix_utc (timestamp);
+      formatted_date = g_date_time_format (time_obj, "%a %b %d %H:%M:%S %Y %z");
+      g_date_time_unref (time_obj);
+      time_obj = NULL;
+
+      formatted_metadata = g_variant_print (commit_metadata, TRUE);
+      g_variant_unref (commit_metadata);
+      formatted = g_strdup_printf ("commit %s\nSubject: %s\nDate: %s\nMetadata: %s\n\n",
+                                   head, subject, formatted_date, formatted_metadata);
+      g_free (formatted_metadata);
+      g_free (formatted_date);
+      formatted_date = NULL;
+      
+      if (!g_output_stream_write_all (pager, formatted, strlen (formatted), &bytes_written, NULL, error))
+        {
+          g_free (formatted);
+          goto out;
+        }
+      g_free (formatted);
+      
+      body_newline = strchr (body, '\n');
+      do {
+        gsize len;
+        if (!g_output_stream_write_all (pager, "    ", 4, &bytes_written, NULL, error))
+          goto out;
+        len = body_newline ? body_newline - body : strlen (body);
+        if (!g_output_stream_write_all (pager, body, len, &bytes_written, NULL, error))
+          goto out;
+        if (!g_output_stream_write_all (pager, "\n\n", 2, &bytes_written, NULL, error))
+          goto out;
+        body_newline = strchr (body, '\n');
+        if (!body_newline)
+          break;
+        else
+          body_newline += 1;
+      } while (*body_newline);
+
+      if (strcmp (parent, "") == 0)
+        break;
+      g_free (head);
+      head = g_strdup (parent);
+    }
+
+  if (!g_output_stream_close (pager, NULL, error))
+    goto out;
+  ret = TRUE;
+ out:
+  if (context)
+    g_option_context_free (context);
+  if (commit)
+    g_variant_unref (commit);
+  g_clear_object (&repo);
+  return ret;
+}
diff --git a/src/ot-builtin-show.c b/src/ot-builtin-show.c
new file mode 100644 (file)
index 0000000..4b280fb
--- /dev/null
@@ -0,0 +1,90 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include "ot-builtins.h"
+#include "ostree.h"
+
+#include <glib/gi18n.h>
+
+static char *repo_path;
+
+static GOptionEntry options[] = {
+  { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", "repo" },
+  { NULL }
+};
+
+gboolean
+ostree_builtin_show (int argc, char **argv, const char *prefix, GError **error)
+{
+  GOptionContext *context;
+  gboolean ret = FALSE;
+  OstreeRepo *repo = NULL;
+  int i;
+  const char *target;
+  OstreeSerializedVariantType type;
+  GVariant *variant = NULL;
+  char *formatted_variant = NULL;
+
+  context = g_option_context_new ("- Output a metadata object");
+  g_option_context_add_main_entries (context, options, NULL);
+
+  if (!g_option_context_parse (context, &argc, &argv, error))
+    goto out;
+
+  if (repo_path == NULL)
+    repo_path = ".";
+
+  repo = ostree_repo_new (repo_path);
+  if (!ostree_repo_check (repo, error))
+    goto out;
+
+  if (argc < 2)
+    {
+      target = ostree_repo_get_head (repo);
+      if (!target)
+        {
+          g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                               "No arguments specified and no HEAD exists");
+          goto out;
+        }
+    }
+  else
+    target = argv[1];
+
+  if (!ostree_repo_load_variant (repo, target, &type, &variant, error))
+    goto out;
+
+  g_print ("Object: %s\nType: %d\n", target, type);
+  formatted_variant = g_variant_print (variant, TRUE);
+  g_print ("%s\n", formatted_variant);
+  ret = TRUE;
+ out:
+  if (context)
+    g_option_context_free (context);
+  g_clear_object (&repo);
+  if (variant)
+    g_variant_unref (variant);
+  g_free (formatted_variant);
+  return ret;
+}
diff --git a/src/ot-builtins.h b/src/ot-builtins.h
new file mode 100644 (file)
index 0000000..7e6d7e7
--- /dev/null
@@ -0,0 +1,49 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#ifndef __OSTREE_BUILTINS__
+#define __OSTREE_BUILTINS__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+  OSTREE_BUILTIN_FLAG_NONE = 0,
+} OstreeBuiltinFlags;
+
+typedef struct {
+  const char *name;
+  gboolean (*fn) (int argc, char **argv, const char *prefix, GError **error);
+  int flags; /* OstreeBuiltinFlags */
+} OstreeBuiltin;
+
+gboolean ostree_builtin_checkout (int argc, char **argv, const char *prefix, GError **error);
+gboolean ostree_builtin_commit (int argc, char **argv, const char *prefix, GError **error);
+gboolean ostree_builtin_init (int argc, char **argv, const char *prefix, GError **error);
+gboolean ostree_builtin_log (int argc, char **argv, const char *prefix, GError **error);
+gboolean ostree_builtin_link_file (int argc, char **argv, const char *prefix, GError **error);
+gboolean ostree_builtin_fsck (int argc, char **argv, const char *prefix, GError **error);
+gboolean ostree_builtin_show (int argc, char **argv, const char *prefix, GError **error);
+
+G_END_DECLS
+
+#endif
index 41c7ffbe0410ccbda606b4f32d5cb98633a7432a..2cfebae3d15e56cf9fe9aab21ed2ba166bea5d97 100644 (file)
@@ -20,7 +20,7 @@
 
 TMPDIR=${TMPDIR:-/tmp}
 export TMPDIR
-test_tmpdir=`mktemp -d "$TMPDIR/hacktree-tests.XXXXXXXXXX"`
+test_tmpdir=`mktemp -d "$TMPDIR/ostree-tests.XXXXXXXXXX"`
 cd "$test_tmpdir"
 touch "$test_tmpdir/.test$$"
 
@@ -57,12 +57,12 @@ setup_test_repository1 () {
     echo second > secondfile
 
     mkdir ../repo
-    ht_repo="--repo=../repo"
-    export ht_repo
-    hacktree init $ht_repo
-    hacktree commit $ht_repo -s "Test Commit 1" -b "Commit body first" --add=firstfile
-    hacktree commit $ht_repo -s "Test Commit 2" -b "Commit body second" --add=secondfile
-    hacktree fsck -q $ht_repo
+    ot_repo="--repo=../repo"
+    export ot_repo
+    ostree init $ot_repo
+    ostree commit $ot_repo -s "Test Commit 1" -b "Commit body first" --add=firstfile
+    ostree commit $ot_repo -s "Test Commit 2" -b "Commit body second" --add=secondfile
+    ostree fsck -q $ot_repo
 }
 
 setup_test_repository2 () {
@@ -82,13 +82,13 @@ setup_test_repository2 () {
     cd ..
     mkdir repo
     cd repo
-    ht_repo="--repo=`pwd`"
+    ot_repo="--repo=`pwd`"
     cd ../files
-    export ht_repo
-    hacktree init $ht_repo
-    hacktree commit $ht_repo -s "Test Commit 1" -b "Commit body first" --add=firstfile
-    hacktree commit $ht_repo -s "Test Commit 2" -b "Commit body second" --add=baz/cow  --add=baz/saucer --add=baz/deeper/ohyeah --add=baz/another/y
-    hacktree fsck -q $ht_repo
+    export ot_repo
+    ostree init $ot_repo
+    ostree commit $ot_repo -s "Test Commit 1" -b "Commit body first" --add=firstfile
+    ostree commit $ot_repo -s "Test Commit 2" -b "Commit body second" --add=baz/cow  --add=baz/saucer --add=baz/deeper/ohyeah --add=baz/another/y
+    ostree fsck -q $ot_repo
 }
 
 trap 'die' EXIT
index 5cdbac3db85830c0f5aa2798a626a93c1f6f0671..b035045fd14af30dd66c4b1159f3c8f07ee76a15 100755 (executable)
@@ -31,15 +31,15 @@ echo moo > files/cow
 
 mkdir repo
 cd repo
-hacktree init
+ostree init
 echo 'ok init'
-hacktree fsck -q
+ostree fsck -q
 echo 'ok fsck'
-hacktree link-file $files/foo
+ostree link-file $files/foo
 echo 'ok link'
-hacktree fsck -q
+ostree fsck -q
 echo 'ok link-fsk'
-hacktree link-file $files/cow
-hacktree fsck -q
+ostree link-file $files/cow
+ostree fsck -q
 echo 'ok link-fsk2'
 
index 436b41be9d25fc7266017d16f442cce8009ec90f..7ab5079822ae6a599ade4772a0e8d36e0fba0b4e 100755 (executable)
@@ -29,9 +29,9 @@ cd files
 echo hello > yy
 
 mkdir ../repo
-hacktree init --repo=../repo
+ostree init --repo=../repo
 echo 'ok init'
-hacktree fsck --repo=../repo -q
+ostree fsck --repo=../repo -q
 echo 'ok fsck'
-hacktree link-file --repo=../repo yy
+ostree link-file --repo=../repo yy
 echo 'ok link'
index a8f8d2e926cec7fbd871f7100e1e24ff88e8cb0a..b181a27902f1b5f18f8e388b2d0df4937c3b7ff5 100755 (executable)
@@ -30,9 +30,9 @@ echo hello > yy
 
 mkdir ../repo
 repo="--repo=../repo"
-hacktree init $repo
+ostree init $repo
 echo 'ok init'
-hacktree commit $repo -s "Test Commit" -b "Commit body" --add=yy
+ostree commit $repo -s "Test Commit" -b "Commit body" --add=yy
 echo 'ok commit'
-hacktree fsck -q $repo
+ostree fsck -q $repo
 echo 'ok fsck'
index 9036fba2ee4006cd9609f382ab8cbf379d5d671a..1ba31ec2823a6871c3e9ffb22d4fb6dc33f38d9c 100755 (executable)
@@ -31,11 +31,11 @@ echo second > secondfile
 
 mkdir ../repo
 repo="--repo=../repo"
-hacktree init $repo
+ostree init $repo
 echo 'ok init'
-hacktree commit $repo -s "Test Commit 1" -b "Commit body first" --add=firstfile
+ostree commit $repo -s "Test Commit 1" -b "Commit body first" --add=firstfile
 echo 'ok commit 1'
-hacktree commit $repo -s "Test Commit 2" -b "Commit body first" --add=secondfile
+ostree commit $repo -s "Test Commit 2" -b "Commit body first" --add=secondfile
 echo 'ok commit 2'
-hacktree fsck -q $repo
+ostree fsck -q $repo
 echo 'ok fsck'
index fa494e3cb1003ba502c6e3fabe561e5de1281fcb..5b30b6fe567bb08fc18abf302a9b2a1fa6fcf3ba 100755 (executable)
@@ -26,7 +26,7 @@ echo '1..3'
 
 setup_test_repository1
 echo 'ok setup'
-hacktree checkout $ht_repo HEAD $test_tmpdir/checkout1-head
+ostree checkout $ot_repo HEAD $test_tmpdir/checkout1-head
 echo 'ok checkout cmd'
 cd $test_tmpdir/checkout1-head
 assert_has_file firstfile
index f7f51827b141471a331106ff51a626d1f7b8318d..d577ce704c8d371443b2498eb9d60bc2b57b0a9d 100755 (executable)
@@ -26,7 +26,7 @@ echo '1..5'
 
 setup_test_repository2
 echo 'ok setup'
-hacktree checkout $ht_repo HEAD $test_tmpdir/checkout2-head
+ostree checkout $ot_repo HEAD $test_tmpdir/checkout2-head
 echo 'ok checkout cmd'
 cd $test_tmpdir/checkout2-head
 assert_has_file firstfile
index 2eada21853bbb4e193e4eb60220725cc8b32f7dd..926da72ba0ddfb4fe3058b237b08ce6f4f1a4714 100755 (executable)
@@ -25,14 +25,14 @@ set -e
 echo '1..4'
 
 setup_test_repository2
-hacktree checkout $ht_repo HEAD $test_tmpdir/checkout2-head
+ostree checkout $ot_repo HEAD $test_tmpdir/checkout2-head
 echo 'ok setup'
 cd $test_tmpdir/checkout2-head
-hacktree commit -s delete $ht_repo -r firstfile
+ostree commit -s delete $ot_repo -r firstfile
 echo 'ok rm firstfile'
 assert_has_file firstfile  # It should still exist in this checkout
 cd $test_tmpdir
-hacktree checkout $ht_repo HEAD $test_tmpdir/checkout3-head
+ostree checkout $ot_repo HEAD $test_tmpdir/checkout3-head
 echo 'ok checkout 3'
 cd $test_tmpdir/checkout3-head
 assert_not_has_file firstfile
index b2cb9351820d5db58106053132be1a598f8a32da..16a688472ae6437767ab32fc034b1c1e7bd4646d 100755 (executable)
@@ -25,7 +25,7 @@ set -e
 echo "1..2"
 
 setup_test_repository2
-hacktree checkout $ht_repo HEAD $test_tmpdir/checkout2-head
+ostree checkout $ot_repo HEAD $test_tmpdir/checkout2-head
 cd $test_tmpdir/checkout2-head
 mkdir -p a/nested/tree
 echo one > a/nested/tree/1
@@ -39,9 +39,9 @@ mkdir -p another/nested/tree
 echo anotherone > another/nested/tree/1
 echo whee2 > another/whee
 # FIXME - remove grep for .
-find | grep -v '^\.$' | hacktree commit $ht_repo --from-stdin -s "From find"
+find | grep -v '^\.$' | ostree commit $ot_repo --from-stdin -s "From find"
 echo "ok commit stdin"
-hacktree checkout $ht_repo HEAD $test_tmpdir/checkout3-head
+ostree checkout $ot_repo HEAD $test_tmpdir/checkout3-head
 cd $test_tmpdir/checkout3-head
 assert_has_file a/nested/2
 assert_file_has_content a/nested/2 'two2'
index 60316ce6b131327ceb3aa4905ae72ded5f60dcfe..d75893e0dfe8abd1ed99f592ea89d746546ebc0c 100755 (executable)
@@ -22,10 +22,10 @@ set -e
 
 . libtest.sh
 
-echo "1..2"
+echo "1..1"
 
 setup_test_repository2
-hacktree log $ht_repo > $test_tmpdir/log.txt
+ostree log $ot_repo > $test_tmpdir/log.txt
 assert_file_has_content $test_tmpdir/log.txt "Test Commit 1"
 assert_file_has_content $test_tmpdir/log.txt "Test Commit 2"
 echo "ok log"